import asyncio
import bpy

timer = None

# https://blenderartists.org/t/running-background-jobs-with-asyncio/673805
class AsyncLoop(bpy.types.Operator):
    bl_idname = "asyncio.loop"
    bl_label = "Runs the asyncio main loop"

    period = bpy.props.FloatProperty(name="Period",
        description="Time between two asyncio beats",
        default=0.01, subtype="UNSIGNED", unit="TIME")

    def execute(self, context):
        self.invoke(context, None)
        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        global timer
        wm = context.window_manager
        wm.modal_handler_add(self)
        timer = wm.event_timer_add(.01, window=context.window)

    def modal(self, context, event):
        global timer
        if not timer:
            return {'FINISHED'}
        elif event.type != 'TIMER':
            return {'PASS_THROUGH'}
        else:
            loop = asyncio.get_event_loop()
            loop.stop()
            loop.run_forever()
            return {'RUNNING_MODAL'}
        
def runAsync(task):
    asyncio.ensure_future(task)
    # Defer the operator call to the next event cycle
    # to avoid issues if runAsync is called from a draw context.
    def timer_callback():
        bpy.ops.asyncio.loop()
        return None # Explicitly return None
    bpy.app.timers.register(timer_callback, first_interval=0)

def register():
    unregister()
    try:
        bpy.utils.register_class(AsyncLoop)
    except:
        pass

def unregister():
    try:
        bpy.utils.unregister_class(AsyncLoop)
    except:
        pass