0

I have been using threading for years but it came out of fashion, probably for very good reasons. I therefore tried to understand how this thing works.

My solution with threading

Consider the threading code below:

import threading
import time

def say_wait_10():
    print("from say_wait_10 start")
    time.sleep(10)
    print("from say_wait_10 end")

def say():
    print("from say")

threading.Thread(target=say_wait_10).start()
threading.Thread(target=say).start()

The idea is to start a task that takes time (say_wait_10()), let it run on its own, and in parallel start a short lived one (say()), that also runs on its own. They are independent, one does not block the other (this may be more or less effective depending on whether the task is CPU or I/O intensive).

The output is, as expected

from say_wait_10 start
from say
from say_wait_10 end

What I tried with asyncio

I now tried to convert that into asyncio, following the documentation. I came up with

import asyncio

async def say_wait_10():
    print("from say_wait_10 start")
    await asyncio.sleep(10)
    print("from say_wait_10 end")

async def say():
    print("from say")

async def main():
    await asyncio.create_task(say_wait_10())
    await asyncio.create_task(say())

asyncio.run(main())

How I understood the code above is that asyncio.create_task(say_wait_10()) creates a task that immediately starts to run on its own. The await is the equivalent of threading.Thread.join() to make sure the main code waits for the coroutine (or task in the example above) has time to finish.

The output is

from say_wait_10 start
from say_wait_10 end
from say

Ah, so the code in the task ran synchronously and only after it was done, say() was called. Where is the asynch part in this?

I then thought that maybe the await actually means "to wait" (for the task to finish before going further) so I removed it, but this just started the program, ran whatever was runnable during the time of the execution of the main program and then it was over

from say_wait_10 start
from say

My question

How to replicate my threading code - I would really appreciate very much an explanation of what is going on in the code so that I can grasp the philosophy of asyncio before going further.

Alternatively, a pointer to a tutorial that would walk me from threading to asyncio would be awesome.

Note

If this can help, I have no problems with the idea of Promises in JS, despite having just a very superficial understanding of JS (that is, more superficial than the one I have in Python - I am just an amateur developer). In JS, due to the place the code runs, I do not have problems to imagine the fact that "things happen when they happen, and then they update some part of the DOM (which is the typical use for me))

WoJ
  • 27,165
  • 48
  • 180
  • 345

1 Answers1

1

The awaits are your problem. await is blocking and so you don’t schedule say until after say_wait_10. There are a couple of ways to accomplish what you want.

The first is some variation of

task = create_task(say_wait_10())
await say()
await task

This get a little messy if you have more than two tasks are less control over how long they take. The other option is

await gather(say_wait_10(), say())
user4815162342
  • 141,790
  • 18
  • 296
  • 355
dirn
  • 19,454
  • 5
  • 69
  • 74
  • Thank you - `gather` does the trick indeed. Is this an equivalent of a combined `start()` and `join()` in `threading`, then? (from the functionality perspective, I know `asyncio` runs in a single thread). That would actually lead to code that is shorter in my case. The problem is that `asyncio.run()` is needed so that the whole `asyncio`thread starts so I cannot have something like a thread I start (and that does things) and continue with my synchronous code. – WoJ Jan 31 '21 at 13:58
  • Right, running the event loop is a blocking operation. If you want a background thread that is doing something outside of the loop, you should take a look at `run_in_executor`. – dirn Jan 31 '21 at 15:00
  • @WoJ Asyncio is not a drop-in replacement for threading, it's a different paradigm that requires the whole program to be written so as to observe it. – user4815162342 Jan 31 '21 at 15:01
  • @user4815162342: I was actually just reading another of your answers (on `run_in_executor`). I see that `asyncio` requires manual control - in the sense that I cannot have for instance two loops that each `print(i)` in a `for i in range(10)`, started concurrently, and expect that the outputs will be mixed. The first loop will need to finish, and then the other - becasie I never manually state that it can "let go" for other coroutines to jump in (which is completely different from `threading` where I do not have any control) – WoJ Jan 31 '21 at 15:14
  • @WoJ Correct, and that difference is [intentional](https://stackoverflow.com/a/20218758/1600898). It's best to think of asyncio as a very sophisticated replacement for [twisted](https://twistedmatrix.com/), not a (direct) replacement for threading. – user4815162342 Jan 31 '21 at 16:00
  • @user4815162342: ah, excellent, thanks. This finally clarifies my confusion - as I was looking at asyncio as a better/faster/whatever threading, and not a completely different philosophical approach. I believe (or hope) that threading is not dying then. – WoJ Jan 31 '21 at 17:10
  • @WoJ Yeah, threading is not going anywhere, and the existence of `run_in_executor` on one side and `run_coroutine_threadsafe` on the other, shows that threading and asyncio will coexist for quite some time to come. But asyncio does have many advantages, especially the possibility of cancelation and fewer race conditions (because yield points are fewer and are visible), so it's a good idea to look into it. – user4815162342 Jan 31 '21 at 17:12