[Tutor] asyncio or threading

Danny Yoo dyoo at hashcollision.org
Mon Feb 15 21:24:50 EST 2016

> I just wanted an opinion on the subject, asyncio and threading both seem to
> do the same job, but i feel threading is better because of the multiple ways
> i can control it.
> Just want get the groups opinion based on there experience in using asyncio
> or threading in real life problems.

I just wanted to chime in: the decision here is more accurately
described as: "synchronous" vs "asynchronous" style.

In a synchronous programming model, functions may block if they need
to do work that takes a while to complete.  By "block", we expect
control flow to stay there until the long-running operation finishes.
One repercussion of this is that it's easy to express the idea of
doing a sequence of staged operations, each of which might take a
while to complete.  e.g.

    stage1Results = stage1()

An advantage of this approach is that it's fairly clear what the
sequence of events will be: stage1 gets called, and when it's done,
stage2 is next in line.

A disadvantage is that if we forget that functions can block, then we
may prevent our program from making quick progress if there's only a
single flow-of-control through our program.

A major role of threading in Python is to allow the user to create
multiple flows of control through a program, all running concurrently,
so that even if one thread of control is blocking on a long-running
function, this may not necessarily stop other flows of control from
making progress toward some goal.


In an asynchronous programming model, functions are not allowed to
block: they must return immediately, with progress deferred some time
later in the future.

In order to express the idea of doing a sequence of staged operations,
where subsequent stages might depend on the results of prior stages,
we need to express to the system "what to do next" when the
long-running process finally has finished.  And unlike in the blocking
case, it's not possible for us to just say:

    stage1Results = stage1_nonblocking()

because the value of stage1Results may not be ready yet!

So in an asynchronous programming model, we need to adjust our
programs to handle a world where no function is ever allowed to block
execution, and yet we want to sequence code such that a later stage
can depend on results of a previous stage.  And, of course, we need to
delay "what do do next" until the value is actually ready to be used.

One mechanism (but not the only one!) we can use to express the "what
to do next" is the "callback", which is a function that is to be
called when the progress from a call is complete.

In callback style, we'd express the staged pseudocode like this:

    def handleStage1Results(stage1Results):


where we pass function objects around to the function that will take a
long time to finish its work.  We expect our callback functions to be
"called back" later by some party after some point.  In many
asynchronous I/O systems, the responsible party is some event loop
that keeps track of what non-blocking calls are still making progress
and which ones are done.

An advantage of this approach is that we don't need multiple flows of
control to do concurrent programming.  Some language environments
don't provide a mechanism for creating multiple flows of control, and
hence are essentially forced into the asynchronous programming model
because they have no other choice.

A disadvantage of asynchronous programming is that it can twist the
structure of programs.  Not only does it make programs less easy to
read, but they can also be harder to debug.  Concretely: we might lose
useful, simple stack traces when errors occur, depending on how
asynchronous programs are implemented, because parts of the stack can
be eliminated since functions never block.

(It's not impossible to get good stack traces in an asynchronous
programming model.  It's just that it's something that we can't take
for granted.  See:
for an explanation in the context of JavaScript.)

More information about the Tutor mailing list