Search code examples
pythonpython-asynciopython-multithreadingpysimplegui

Why does this asyncio call pause execution?


Here is my code:

async def runTaskWrapped(options):
    layoutz = [[sg.Text("Running...", key="runstatus")]];
    windowz = sg.Window("New Task", layoutz);
    x = threading.Thread(target=runTask, args=(options,));
    x.start();
    startTime = time.time();
    while True:
        eventz, valuesz = windowz.read(timeout=100)
        if eventz == sg.WIN_CLOSED:
            if x.is_alive():
                continue
            break
        if x.is_alive() == False:
            x.join()
            windowz.FindElement('runstatus').Update(value='Done! Check the new log.txt for more info.');
            break;
        else:
            windowz.FindElement('runstatus').Update(value='Running... (' + str(math.floor(time.time()-startTime)) + ')')

asyncio.run(runTaskWrapped(options));

I have tried everything and it still seems that execution pauses after asyncio.run(runTaskWrapped(options));

Any idea why this may be?

EDIT: I tried threading.Thread, and although it didn't pause execution, pysimplegui (imported as sg) didnt do anything and no window showed up for it like it does when called synchronously.

I tried trio too, but trio paused execution. trio.run(runTaskWrapped, options);


Solution

  • When you call asyncio.run(some_function()), your program will not go to the next line until some_function() returns. In your case, runTaskWrapped doesn't return until you execute one of its "break" statements.

    We deal with this sort of thing all the time. If you call any function f(), your program won't continue until f() returns. That's a familiar concept.

    What's different about asyncio is that it creates a loop of its own, called the event loop, and launches some_function() from inside that loop. That allows you to start other tasks from within some_function(), and those other tasks get a chance to execute when some_function() encounters an "await" statement. That's a powerful concept if that's what you need. But it's only useful if you have two or more tasks that need to wait on external resources, like a network or a serial communications link, and one of the tasks can proceed while the other is waiting.

    Your function runTaskWrapped does not contain any "await" statements. So asyncio creates an event loop, hands control to runTaskWrapped. That's a blind alley. It is more-or-less an infinite loop and doesn't "await" anything. Therefore there is no way out of runTaskWrapped, and your program is effectively dead at that point.

    In order to make use of asyncio you must structure your program to have more than one task containing "await"s.

    You are writing a GUI program, which typically means that it already has an event loop of its own. In some cases it is possible to run the GUI's event loop and the asyncio event loop together, but unless you have a specific need to do this it doesn't gain you anything.

    You are also trying to use asyncio with multiple threads, and although this is possible it needs to be done quite carefully. You can start other threads just as in any other Python program, but the presence of those other threads doesn't change what happens in your main thread. You must specifically write code to synchronize events between threads.

    No matter what you do in those other threads, asyncio.run(some_function()) will not return until its argument is finished.