Package: asynq-nocython
asynqis a library for asynchronous programming in Python with a focus on batching requests to
external services. It also provides seamless interoperability with synchronous code, support for
asynchronous context managers, and tools to make writing and testing asynchronous code easier.asynqwas developed at Quora and is a core component of Quora’s architecture. See the original blog
posthere.The most important use case forasynqis batching. For many storage services (e.g., memcache,
redis) it is far faster to make a single request that fetches many keys at once than to make
many requests that each fetch a single key. Theasynqframework makes it easy to write code
that takes advantage of batching without radical changes in code structure from code that does not
use batching.For example, synchronous code to retrieve the names of the authors of a list of Quora answers may
look like this:defall_author_names(aids):uids=[author_of_answer(aid)foraidinaids]names=[name_of_user(uid)foruidinuids]returnnamesHere, each call toauthor_of_answerandname_of_userwould result in a memcache request.
Converted to useasynq, this code would look like:@async()defall_author_names(aids):uids=yield[author_of_answer.async(aid)foraidinaids]names=yield[name_of_user.async(uid)foruidinuids]result(names);returnAllauthor_of_answercalls will be combined into a single memcache request, as will all of thename_of_usercalls.FuturesFutures are the basic building blocks ofasynq’s programming model. The scheduler keeps track
of futures and attempts to schedule them in an efficient way.asynquses its own hierarchy of
Future classes, rooted inasynq.FutureBase. Futures have a.value()method that computes
their value if necessary and then returns it.The following are the most important Future classes used inasynq:AsyncTask, a Future representing the execution of an asynchronous function (see below).
Normally created by calling.async()on an asynchronous function.ConstFuture, a Future whose value is known at creation time. This is useful when you need
to pass a Future somewhere, but no computation is actually needed.BatchBaseandBatchItemBase, the building blocks for doing batching. See below for
details.Decorators and asynchronous functionsasynq’s asynchronous functions are implemented as Python generator functions. Every time an
asynchronous functions yields one or more Futures, it cedes control the asynq scheduler, which will
resolve the futures that were yielded and continue running the function after the futures have been
computed.Returning a value from an asynchronous function is hard in Python 2 because generators are not
allowed to return a value.asynqprovides the specialresult()function that can be used to
return a value from an asynchronous function; it is implemented by raising a custom exception
that is caught by the scheduler. At Quora, we instead use a patched Python 2 binary that does
support returning a value from a generator.The framework requires usage of the@async()decorator on all asynchronous functions. This
decorator wraps the generator function so that it can be called like a normal, synchronous function.
It also creates a.asyncattribute on the function that allows calling the function
asynchronously. Calling this attribute will return anAsyncTaskobject corresponding to the
function.You can call an asynchronous function synchronously like this:result=async_fn(a,b)and asynchronously like this:result=yieldasync_fn.async(a,b)Callingasync_fn.async(a,b).value()has the same result asasync_fn(a, b).The decorator has apure=Trueoption that disables the.asyncattribute and instead makes
the function itself asynchronous, so that calling it returns anAsyncTask. We recommend to use
this option only in special cases like decorators for asynchronous functions.asynqalso provides an@async_proxy()decorator for functions that return a Future
directly. Functions decorated with@async_proxy()look like@async()functions externally.
An example use case is a function that takes either an asynchronous or a synchronous function,
and calls it accordingly:@async_proxy()defasync_call(fn,*args,**kwargs):ifis_async_fn(fn):# Returns an AsyncTaskreturnfn.async(*args,**kwargs)returnConstFuture(fn(*args,**kwargs))BatchingBatching is at the core of what makesasynquseful. To implement batching, you need to subclassasynq.BatchItemBaseandasynq.BatchBase. The first represents a single entry in a batch
(e.g., a single memcache key to fetch) and the second is responsible for executing the batch when
the scheduler requests it.Batch items usually do not require much logic beyond registering themselves with the currently
active batch in__init__. Batches need to override the_try_switch_active_batchmethod,
which changes the batch that is currently active, and the_flushmethod that executes it.
This method should call.set_value()on all the items in the batch.An example implementation of batching for memcache is in theasynq/examples/batching.pyfile.
The framework also provides aDebugBatchItemfor testing.Most users ofasynqshould not need to implement batches frequently. At Quora, we use
thousands of asynchronous functions, but only fiveBatchBasesubclasses.Contextsasynqprovides support for Python context managers that are automatically activated and
deactivated when a particular task is scheduled. This feature is necessary because the scheduler
can schedule tasks in arbitrary order. For example, consider the following code:@async()defshow_warning():yielddo_something_that_creates_a_warning.async()@async()defsuppress_warning():withwarnings.catch_warnings():yieldshow_warning.async()@async()defcaller():yieldshow_warning.async(),suppress_warning.async()This code should show only one warning, because only the second call toshow_warningis within
acatch_warnings()context, but depending on how the scheduler happens to execute these
functions, the code that shows the warning may also be executed whilecatch_warnings()is
active.To remedy this problem, you should use anAsyncContext, which will be automatically paused when
the task that created it is no longer active and resumed when it becomes active again. Anasynq-compatible version ofcatch_warningswould look something like this:classcatch_warnings(asynq.AsyncContext):defpause(self):stop_catching_warnings()defresume(self):start_catching_warnings()DebuggingBecause theasynqscheduler is invoked every time an asynchronous function is called, and it
can invoke arbitrary other active futures, normal Python stack traces become useless in a
sufficiently complicated application built onasynq. To make debugging easier, the framework
provides the ability to generate a customasynqstack trace, which shows how each active
asynchronous function was invoked.Theasynq.debug.dump_asynq_stack()method can be used to print this stack, similar totraceback.print_stack(). The framework also registers a hook to print out theasynqstack
when an exception happens.Toolsasynqprovides a number of additional tools to make it easier to write asynchronous code. Some
of these are in theasynq.toolsmodule. These tools include:asynq.async_callcalls a function asynchronously only if it is asynchronous. This can be
useful when calling an overridden method that is asynchronous on some child classes but not on others.asynq.tools.call_with_contextcalls an asynchronous function within the provided context
manager. This is helpful in cases where you need to yield multiple tasks at once, but only one
needs to be within the context.asynq.tools.afilterandasynq.tools.asortedare equivalents of the standardfilterandsortedfunctions that take asynchronous functions as their filter and compare functions.asynq.tools.acached_per_instancecaches an asynchronous instance method.asynq.tools.deduplicateprevents multiple simultaneous calls to the same asynchronous
function.Theasynq.mockmodule is an enhancement to the standardmockmodule that makes it
painless to mock asynchronous functions. Without this module, mocking any asynchronous function
will often also require mocking its.asyncattribute. We recommend usingasynq.mock.patchfor all mocking in projects that useasynq.Theasynq.generatormodule provides an experimental implementation of asynchronous
generators, which can produce a sequence of values while also usingasynq’s batching support.Compatibilityasynqruns on Python 2.7 and Python 3.ContributorsAlex Yakunin,Jelle Zijlstra,Manan Nayak,Martin Michelsen,Shrey Banga,Suren Nihalaniand
other engineers at Quora.