Package: asynkit
asynkit: A toolkit for Python coroutinesThis module provides some handy tools for those wishing to have better control over the
way Python'sasynciomodule does things.Helper tools for controlling coroutine execution, such asCoroStartandMonitorUtility classes such asGeneratorObjectCoroutine helpers suchcoro_iter()and theawaitmethod()decoratorHelpers to runasynccode fromnon-asynccode, such asawait_sync()andaiter_sync()Scheduling helpers forasyncio, and extended event-loop implementationseagerexecution of TasksExprerimental support forPriority Schedulingof TasksOther experimental features such astask_interrupt()Limited support foranyioandtrio.InstallationpipinstallasynkitCoroutine Toolseager()- lower latency IODid you ever wish that yourcoroutinesstarted right away, and only returned control to
the caller once they become blocked?  Like the way theasyncandawaitkeywords work in theC#language?Now they can. Just decorate or convert them withacynkit.eager:@asynkit.eagerasyncdefget_slow_remote_data():result=awaitexecute_remote_request()returnresult.important_dataasyncdefmy_complex_thing():# kick off the request as soon as possiblefuture=get_slow_remote_data()# The remote execution may now already be in flight. Do some work taking timeintermediate_result=awaitsome_local_computation()# wait for the result of the requestreturncompute_result(intermediate_result,awaitfuture)By decorating your function witheager, the coroutine will start executingright awayand
control will return to the calling function as soon as itsuspends,returns, orraisesan exception. In case it is suspended, aTaskis created and returned, ready to resume
execution from that point.Notice how, in either case, control is returneddirectlyback to the
calling function, maintaining synchronous execution. In effect, conventional code
calling order is maintained as much as possible. We call thisdepth-first-execution.This allows you to prepare and dispatch long running operationsas soon as possiblewhile
still being able to asynchronously wait for the result.asynkit.eagercan also be used directly on the returned coroutine:log=[]asyncdeftest():log.append(1)awaitasyncio.sleep(0.2)# some long IOlog.append(2)asyncdefcaller(convert):dellog[:]log.append("a")future=convert(test())log.append("b")awaitasyncio.sleep(0.1)# some other IOlog.append("c")awaitfuture# do nothingasyncio.run(caller(lambdac:c))assertlog==["a","b","c",1,2]# Create a Taskasyncio.run(caller(asyncio.create_task))assertlog==["a","b",1,"c",2]# eagerasyncio.run(caller(asynkit.eager))assertlog==["a",1,"b","c",2]eager()is actually a convenience function, invoking eithercoro_eager()orfunc_eager()(see below) depending on context.
Decorating your function makes sense if youalwaysintend
Toawaitits result at some later point. Otherwise, just apply it at the point
of invocation in each such case.coro_eager(),func_eager()coro_eager()is the magic coroutine wrapper providing theeagerbehaviour:It copies the currentcontextIt initializes aCoroStart()object for the coroutine, starting it in the copied context.If it subsequently isdone()It returnsCoroStart.as_future(), otherwise
it creates and returns aTask(usingasyncio.create_taskby default.)The result is anawaitablewhich can be either directly awaited or passed
toasyncio.gather(). The coroutine is executed in its own copy of the current context,
just as would happen if it were directly turned into aTask.func_eager()is a decorator which automatically appliescoro_eager()to the coroutine returned by an async function.await_sync(), aiter_sync()- Running coroutines synchronouslyIf you are writing code which should work both synchronously and asynchronously,
you can now write the code fullyasyncand then run itsynchronouslyin the absence
of an event loop.  As long as the code doesn'tblock(await unfinishedfutures) and doesn't try to access the event loop, it can successfully be executed.  This helps avoid writing duplicate code.asyncdefasync_get_processed_data(datagetter):data=datagetter()# an optionally async callbackdata=awaitdataifisawaitable(data)elsedatareturnprocess_data(data)# raises SynchronousError if datagetter blocksdefsync_get_processed_data(datagetter):returnasynkit.await_sync(async_get_processed_data(datagetter))This sort of code might previously have been written thus:# A hybrid function, _may_ return an _awaitable_defhybrid_get_processed_data(datagetter):data=datagetter()ifisawaitable(data):# return an awaitable helper closureasyncdefhelper():data=awaitdatareturnprocess_data(data)returnhelperreturnprocess_data(data)# duplicationasyncdefasync_get_processed_data(datagetter):r=hybrid_get_processed_data(datagetter)returnawaitrifisawaitable(r)elserdefsync_get_processed_data(datagetter):r=hybrid_get_processed_data(datagetter)ifisawaitable(r):raiseRuntimeError("callbacks failed to run synchronously")returnrThe above pattern, writing async methods as sync and returning async helpers,
is common in library code which needs to work both in synchronous and asynchronous
context.  Needless to say, it is very convoluted, hard to debug and contains a lot
of code duplication where the same logic is repeated inside async helper closures.Usingawait_sync()it is possible to write the entire logic asasyncmethods and
then simply fail if the code tries to invoke any truly async operations.
If the invoked coroutine blocks, aSynchronousErroris raisedfromaSynchronousAbortexception which
contains a traceback.  This makes it easy to pinpoint the location in the code where the
async code blocked.  If the code tries to access the event loop, e.g. by creating aTask, aRuntimeErrorwill be raised.Thesyncfunction()decorator can be used to automatically wrap an async function
so that it is executed usingawait_sync():>>>@asynkit.syncfunction...asyncdefsync_function():...asyncdefasync_function():...return"look, no async!"...returnawaitasync_function()...>>>sync_function()'look, no async!'>>>theasyncfunction()utility can be used when passing synchronous callbacks to async
code, to make them async.  This, along withsyncfunction()andawait_sync(),
can be used to integrate synchronous code with async middleware:@asynkit.syncfunctionasyncdefsync_client(sync_callback):middleware=AsyncMiddleware(asynkit.asyncfunction(sync_callback))returnawaitmiddleware.run()Using this pattern, one can write the middleware completely async, make it also work
for synchronous code, while avoiding the hybrid functionantipattern.aiter_sync()A helper function is provided, which turns anAsyncIterableinto
a generator, leveraging theawait_sync()method:asyncdefagen():forvinrange(3):yieldvassertlist(aiter_sync(agen()))==[1,2,3]This is useful if using patterns such asGeneratorObjectin a synchronous
application.CoroStartThis class manages the state of a partially run coroutine and is what what powers thecoro_eager()andawait_sync()functions.
When initialized, it willstartthe coroutine, running it until it either suspends, returns, or raises
an exception.  It can subsequently beawaitedto retrieve the result.Similarly to aFuture, it has these methods:done()- returnsTrueif the coroutine finished without blocking. In this case, the following two methods may be called to get the result.result()- Returns thereturn valueof the coroutine orraisesanyexceptionthat it produced.exception()- Returns anyexceptionraised, orNoneotherwise.But more importantly it has these:__await__()- A magic method making it directlyawaitable. If it has already finished, awaiting this coroutine is the same as callingresult(), otherwise it awaits the original coroutine's continued executionas_coroutine()- A helper which returns a propercoroutineobject to await theCoroStartas_future()- Ifdone(), returns aFutureholding its result, otherwise, aRuntimeErroris raised.as_awaitable()- Ifdone(), returnsas_future(), else returnsself.
This is a convenience method for use with functions such asasyncio.gather(), which would otherwise wrap a completed coroutine in aTask.In addition it has:aclose()- Ifnot done(), will throw aGeneratorErrorinto the coroutine and wait for it to finish.  Otherwise does nothing.athrow(exc)- Ifnot done(), will throw the given error into the coroutine and wait for it to raise or return a value.close()andthrow(exc)- Synchronous versions of the above, will raiseRuntimeErrorif the coroutine does not immediately exit.This means that a context manager such asaclosing()can be used to ensure
that the coroutine is cleaned up in case of errors before it is awaited:# start foo() and run until it blocksasyncwithaclosing(CoroStart(foo()))ascoro:...# do things, which may result in an errorreturnawaitcoroCoroStart can be provided with acontextvars.Contextobject, in which case the coroutine will be run using that
context.Context helpercoro_await()is a helper function to await a coroutine, optionally with acontextvars.Contextobject to activate:var1=contextvars.ContextVar("myvar")asyncdefmy_method():var1.set("foo")asyncdefmain():context=contextvars.copy_context()var1.set("bar")awaitasynkit.coro_await(my_method(),context=context)# the coroutine didn't modify _our_ contextassertvar1.get()=="bar"# ... but it did modify the copied contextassertcontext.get(var1)=="foo"This is similar tocontextvars.Context.run()but works for async functions.  This function is
implemented usingCoroStartawaitmethod()This decorator turns the decorated method into aGeneratoras required for__await__methods, which must only returnIteratorobjects.
It does so by invoking thecoro_iter()helper.This makes it simple to make a class instanceawaitableby decorating anasync__await__()method.classAwaitable:def__init__(self,cofunc):self.cofunc=cofuncself.count=0@asynkit.awaitmethodasyncdef__await__(self):awaitself.cofunc()returnself.countself.count+=1asyncdefmain():asyncdefsleeper():awaitasyncio.sleep(1)a=Awaitable(sleeper)assert(awaita)==0# sleep onceassert(awaita)==1# sleep againasyncio.run(main())Unlike a regularcoroutine(the result of calling acoroutine function), an object with an__await__method can potentially be awaited multiple times.The method can also be aclassmethodorstaticmethod:classConstructor:@staticmethod@asynkit.awaitmethodasyncdef__await__():awaitasyncio.sleep(0)returnConstructor()asyncdefconstruct():returnawaitConstructorawaitmethod_iter()An alternative way of creating anawaitmethod, it uses
thecoro_iter()method to to create a coroutine iterator.  It
is provided for completeness.coro_iter()This helper function turns a coroutine function into an iterator.  It is primarily
intended to be used by theawaitmethod_iter()function decorator.Monitors and GeneratorsMonitorAMonitorobject can be used to await a coroutine, while listening forout of bandmessages
from the coroutine.  As the coroutine sends messages, it is suspended, until the caller resumes
awaiting for it.asyncdefcoro(monitor):awaitmonitor.oob("hello")awaitasyncio.sleep(0)awaitmonitor.oob("dolly")return"done"asyncdefrunner():m=Monitor()c=coro(m)whileTrue:try:print(awaitm.aawait(c))breakexceptOOBDataasoob:print(oob.data)which will result in the outputhello
dollydoneFor convenience, theMonitorcan beboundso that the caller does not have
to keep the coroutine around.  Calling the monitor with the coroutine returns aBoundMonitor:asyncdefcoro(m):awaitm.oob("foo")return"bar"m=Monitor()b=m(coro(m))try:awaitbexceptOOBDataasoob:assertoob.data=="foo"assertawaitb=="bar"Notice how theBoundMonitorcan beawaiteddirectly, which is the same as awaitingb.aawait(None).The caller can pass indatato the coroutine via theaawait(data=None)method and
it will become thereturn valueof theMonitor.oob()call in the coroutine.Monitor.athrow()can similarly be used to raise an exception out of theMontitor.oob()call.
Neither data nor an exception can be sent the first time the coroutine is awaited,
only as a response to a previousOOBDataexception.AMonitorcan be used when a coroutine wants to suspend itself, maybe waiting for some external
condition, without resorting to the relatively heavy mechanism of creating, managing and synchronizingTaskobjects.  This can be useful if the coroutine needs to maintain state.  Additionally,
this kind of messaging does not require anevent loopto be present and can can be driven
usingawait_sync()(see below.)Consider the following scenario. Aparserwants to read a line from a buffer, but fails, signalling
this to the monitor:asyncdefreadline(m,buffer):l=buffer.readline()whilenotl.endswith("\n"):awaitm.oob(None)# ask for more data in the bufferl+=buffer.readline()returnlasyncdefmanager(buffer,io):m=Monitor()a=m(readline(m,buffer))whileTrue:try:returnawaitaexceptOOBData:try:buffer.fill(awaitio.read())exceptExceptionasexc:awaita.athrow(exc)In this example,readline()is trivial, but if it were a stateful parser with hierarchical
invocation structure, then this pattern allows the decoupling of IO and the parsing of buffered data, maintaining the state of the parser whilethe callerfills up the buffer.Any IO exception is sent to the coroutine in this example.  This ensures that it cleans
up properly.  Alternatively,aclose()could have been used:m=Monitor()withaclosing(m(readline(m,buffer)))asa:# the aclosing context manager ensures that the coroutine is closed# with `await a.aclose()`# even if we don't finish running it....A standalone parser can also be simply implemented by two helper methods,start()andtry_await().asyncdefstateful_parser(monitor,input_data):whileinput_short(input_data):input_data+=awaitmonitor.oob()# request more# continue parsing, maybe requesting more datareturnawaitparsed_data(monitor,input_data)m:Monitor[Tuple[Any,bytes]]=Monitor()initial_data=b""p=m(stateful_parser(m,b""))awaitp.start()# set the parser running, calling oob()# feed data until a value is returnedwhileTrue:parsed=awaitp.try_await(awaitget_more_data())ifparsedisnotNone:breakThis pattern can even be employed in non-async applications, by using
theawait_sync()method instead of theawaitkeyword to drive theMonitor.For a more complete example, have a look atexample_resp.pyGeneratorObjectAGeneratorObjectbuilds on top of theMonitorto create anAsyncGenerator.  It is in many ways
similar to anasynchronous generatorconstructed using thegenerator functionsyntax.
But whereas those return values using theyieldkeyword,
a GeneratorObject has anayield()method, which means that data can be sent to the generator
by anyone, and not just by usingyield, which makes composing such generators much simpler.TheGeneratorObjectleverages theMonitor.oob()method to deliver theayieldeddata to whomever is iterating over it:asyncdefgenerator(gen_obj):# yield directly to the generatorawaitgen_obj.ayield(1)# have someone else yield to itasyncdefhelper():awaitgen_obj.ayield(2)awaitasyncio.create_task(helper())asyncdefrunner():gen_obj=GeneratorObject()values=[valasyncforvalingen_obj(generator(gen_obj))]assertvalues==[1,2]TheGeneratorObject, when called, returns aGeneratorObjectIteratorwhich behaves in
the same way as anAsyncGeneratorobject.  It can be iterated over and supports theasend(),athrow()andaclose()methods.AGeneratorObjectis a flexible way to asynchronously generate results without
resorting toTaskandQueueobjects.  What is more, it allows this sort
of generating pattern to be used in non-async programs, viaaiter_sync():defsync_runner():gen_obj=GeneratorObject()values=[valforvalinaiter_sync(gen_obj(generator(gen_obj)))]assertvalues==[1,2]Scheduling toolsA set of functions are provided to perform advanced scheduling ofTaskobjects
withasyncio.  They work with the built-in event loop, and also with any event loop
implementing theAbstractSchedulingLoopabstract base class, such as theSchedulingMixinclass which can be used to extend the built-in event loops.Scheduling functionssleep_insert(pos)Similar toasyncio.sleep()but sleeps only forposplaces in the runnable queue.
Whereasasyncio.sleep(0)will place the executing task at the end of the queue, which is
appropriate for fair scheduling, in some advanced cases you want to wake up sooner than that, perhaps
after a specific task.task_reinsert(task, pos)Takes arunnabletask (for example just created withasyncio.create_task()or similar) and
reinserts it at a given position in the queue.
Similarly as forsleep_insert(), this can be useful to achieve
certain scheduling goals.task_switch(task, *, insert_pos=None)Immediately moves the given task to the head of the ready queue and switches to it, assuming it is runnable.
Ifinsert_pos is not None, the current task will be
put to sleep at that position, usingsleep_insert(). Otherwise the current task is put at the end
of the ready queue.  Ifinsert_pos == 1the current task will be inserted directly after the target
task, making it the next to be run.  Ifinsert_pos == 0, the current task will executebeforethe target.task_is_blocked(task)Returns True if the task is waiting for some awaitable, such as a Future or another Task, and is thus not
on the ready queue.task_is_runnable(task)Roughly the opposite oftask_is_blocked(), returns True if the task is neitherdone()norblockedand
awaits execution.create_task_descend(coro)Implements depth-first task scheduling.Similar toasyncio.create_task()this creates a task but starts it running right away, and positions the caller to be woken
up right after it blocks. The effect is similar to usingasynkit.eager()but
it achieves its goals solely by modifying the runnable queue. ATaskis always
created, unlikeeager, which only creates a task if the target blocks.Runnable task helpersA few functions are added to help working with tasks.The following identity applies:asyncio.all_tasks()=(asynkit.runnable_tasks()|asynkit.blocked_tasks()|asyncio.current_task())runnable_tasks(loop=None)Returns a set of the tasks that are currently runnable in the given loopblocked_tasks(loop=None)Returns a set of the tasks that are currently blocked on some future in the given loop.Event Loop toolsAlso provided is a mixin for the built-in event loop implementations in python, providing some primitives for advanced scheduling of tasks.  These primitives are what is used by the
scheduling functions above, and so custom event loop implementations can provide custom
implementations of these methods.SchedulingMixinmixin classThis class adds some handy scheduling functions to the event loop. The are intended
to facilitate some scheduling tricks, particularly switching to tasks, which require
finding items in the queue and re-inserting them at anearlyposition.  Nothing
is assumed about the underlying implementation of the queue.queue_len()- returns the length of the ready queuequeue_find(self, key, remove)- finds and optionally removes an element in the queuequeue_insert_pos(self, pos, element)- inserts an element at positionposthe queuecall_pos(self, pos, ...)- schedules a callback at positionposin the queueConcrete event loop classesConcrete subclasses of Python's built-in event loop classes are provided.SchedulingSelectorEventLoopis a subclass ofasyncio.SelectorEventLoopwith theSchedulingMixinSchedulingProactorEventLoopis a subclass ofasyncio.ProactorEventLoopwith theSchedulingMixinon those platforms that support it.Event Loop PolicyA policy class is provided to automatically create the appropriate event loops.SchedulingEventLoopPolicyis a subclass ofasyncio.DefaultEventLoopPolicywhich instantiates either of the above event loop classes as appropriate.Use this either directly:asyncio.set_event_loop_policy(asynkit.SchedulingEventLoopPolicy())asyncio.run(myprogram())or with a context manager:withasynkit.event_loop_policy():asyncio.run(myprogram())Priority SchedulingFIFO schedulingSince the beginning,schedulingof Tasks inasynciohas always beenFIFO, meaning "first-in, first-out".  This is a design principle which provides a certainfairnessto tasks, ensuring that all tasks run and a certain predictability is achieved with execution.  FIFO is maintained in the following places:In theEvent Loop, where tasks are executed in the order in which they becomerunnableIn locking primitives (such asasyncio.Lockorasyncio.Condition) where tasks are able toacquirethe lock or get notified in the order
in which they arrive.All tasks are treated equally.Theasynkit.experimental.prioritymoduleNote:  This is currently anexperimentalfeature.In pre-emptive system, such as scheduling ofthreadsorprocessesthere is usually some sort ofpriorityinvolved too,
to allow designating some tasks as more important than others, thus requiring more rapid servicing, and others as having
lower priority and thus be relegated to background tasks where other more important work is not pending.Theasynkit.experimental.prioritymodule now allows us to do something similar.You can define thepriorityof Task objects.  A task defining theeffective_priority()method returning
afloatwill get priority treatment in the following areas:When awaiting aPriorityLockorPriorityConditionWhen waiting in to be executed by aPrioritySelectorEventLoopor aPriorityProactorEventLoop.The floating pointpriority valuereturned byeffective_priority()is used to determine the task's priority, withlower
valuesgivinghigher priority(in the same way that low values aresorted beforehigh values).
If this method is missing, the default priority of0.0is assumed.  ThePriorityenum class can be
used for some basic priority values, definingPriority.HIGHas -10.0 andPriority.LOWas 10.0.
In case of identical priority values, FIFO order is respected.The locking primitives provided are fully compatible with the standard
locks inasyncioand also fully support the experimentaltask interruptionfeature.PriorityTaskThis is anasyncio.Tasksubclass which implements theeffective_priority()method.  It can be constructed with aprioritykeyword
or apriority_valueattribute.  It also participates inPriority Inheritance.PriorityLockThis is aasyncio.Locksubclass which respects the priorities of anyTaskobjects attempting to acquire it.  It also participates inPriority Inheritance.PriorityConditionThis is anasyncio.Conditionsubclass which respects the priorities of anyTaskobjects awaiting to be woken up.  Its defaultlockis of typePriorityLock.DefaultPriorityEventLoopThis is anasyncio.AbstractEventLoopsubclass which respects the priorities of any Task objects waiting to be executed.  It also
provides all the scheduling extensions fromAbstractSchedulingLoop.  It also participates inPriority Inheritance.This is either aPrioritySelectorEventLoopor aPriorityProactorEventLoop, both instances of thePrioritySchedulingMixinclass.Priority InversionA well known problem with priority scheduling is the so-calledPriority Inversionproblem.  This implementation addresses that by two different means:Priority InheritanceAPriorityTaskkeeps track of all thePriorityLockobjects it has acquired, and aPriorityLockkeeps track of all theasyncio.Taskobjects waiting to acquire it.  APriorityTask'seffective_priority()method will be the highesteffective_priorityof any
task waiting to acquire a lock held by it.  Thus, a high priority-task which starts waiting for a lock which is held by a
low-priority task, will temporarilypropagateits priority to that task, so that ultimately, thePrioritySchedulingMixinevent
loop with ensure that the previously low-priority task is now executed with the higher priority.This mechanism requires the co-operation of both the tasks, locks and the event-loop to properly function.Priority BoostingThePrioritySchedulingMixinwill regularly do "queue maintenance" and will identify Tasks that have sat around in the queue for
many cycles without being executed.  It will randomly "boost" the priority of these tasks in the queue, so that they have a chance
to run.This mechanism does not require the co-operation of locks and tasks to work, and is in place as a safety mechanism in applications
where it is not feasible to replace all instances ofLocks andTasks with theirpriority_inheritance-aware counterparts.How to use Priority SchedulingTo make use of Priority scheduling, you need to use either the priority scheduling event loop (e.g.DefaultPriorityEventLoop) or a priority-aware synchronization primitive, i.e.PriorityLockorPriorityCondition.  In addition, you needTaskobjects which support theeffective_priority()method, such asPriorityTaskIt is possible to get priority behaviour from locks without having a priority event loop, and
vice versa.  But when using the priority event loop, it is recommended to use the accompanying
lock and task classes which co-operate to providepriority inheritance.A good first step, in your application, is to identify tasks
that perform background work, such as housekeeping tasks, and assign to them thePriority.LOWpriority.Subsequently you may want to identify areas of your application that require more attention than others.  For a web-application's URL handler may elect to temporarily raise the priority (changePriorityTask.priority_value) for certain endpoints to give them better response.This is new territory and it remains to be seen how having priority scheduling in aco-operative`` environment such asasyncio` actually works in practice.Coroutine helpersA couple of functions are provided to introspect the state of coroutine objects. They
work on both regularasynccoroutines,classiccoroutines (usingyield from) andasync generators.coro_is_new(coro)-
Returns true if the object has just been created and hasn't started executing yetcoro_is_suspended(coro)- Returns true if the object is in a suspended state.coro_is_done(coro)- Returns true if the object has finished executing, e.g. by returning or raising an exception.coro_get_frame(coro)- Returns the current frame object of the coroutine, if it has one, orNone.anyiosupportThe library has been tested to work with theanyio.  However, not everything is supported on thetriobackend.
Currently only theasynciobackend can be assumed to work reliably.When using the asyncio backend, the moduleasynkit.experimental.anyiocan be used, to provide "eager"-like
behaviour to task creation.  It will return anEagerTaskGroupcontext manager:fromasynkit.experimental.anyioimportcreate_eager_task_groupfromanyioimportrun,sleepasyncdeffunc(task_status):print("hello")task_status.started("world")awaitsleep(0.01)print("goodbye")asyncdefmain():asyncwithcreate_eager_task_group()astg:start=tg.start(func)print("fine")print(awaitstart)print("world")run(main,backend="asyncio")This will result in the following output:hello
fine
world
goodbye
worldThe first part of the functionfuncis run even before callingawaiton the result fromEagerTaskGroup.start()Similarly,EagerTaskGroup.start_soon()will run the provided coroutine up to its first blocking point before
returning.triolimitationstriodiffers significantly fromasyncioand therefore enjoys only limited support.The event loop is completely different and proprietary and so the event loop extensions don't work
fortrio.CoroStartwhen used withTaskobjects, such as by usingEagerTaskGroup,
does not work reliably withtrio.
This is because the synchronization primitives
are not based onFutureobjects but rather performTask-based actions both before going to sleep
and upon waking up.  If aCoroStartinitially blocks on a primitive such asEvent.wait()orsleep(x)it will be surprised and throw an error when it wakes up on in a differentTaskthan when it was in when it fell asleep.CoroStartworks by intercepting aFuturebeing passed up via theawaitprotocol to
the event loop to perform the task scheduling.  If any part of the task scheduling has happened
before this, and thecontinuationhappens on a differentTaskthen things may break
in various ways.   Forasyncio, the event loop never sees theFutureobject untilas_coroutine()has been called and awaited, and so if this happens in a new task, all is good.Experimental featuresSome features are currently available experimentally.  They may work only on some platforms or be experimental in nature, not stable or mature enough to be officially part of the libraryTask InterruptionMethods are provided to raise exceptions on aTask.  This is somewhat similar totask.cancel()but different:The caller specifies the exception instance to be raised on the task.The target task made to run immediately, precluding interference with other operations.The exception does not propagate into awaited objects.  In particular, if the task
isawaitinganother task, the wait is interrupted, but that other task is not otherwise
affected.A task which is blocked, waiting for a future, is immediately freed and scheduled to run.
If the task is already scheduled to run, i.e. it isnew, or the future has triggered but
the task hasn't become active yet, it is still awoken with an exception.Please note the following cases:The Python library in places assumes that the only exception that can be
raised out of awaitables isCancelledError.  In particular, there are edge cases
inasyncio.Lock,asyncio.Semaphoreandasyncio.Conditionwhere raising something
else when acquiring these primitives will leave them in an incorrect state.Therefore, we provide a base class,InterruptError, deriving fromCancelledErrorwhich
should be used for interrupts in general.However, currentlyasyncio.Conditionwill not correctly pass on such a subclass
forwait()in all cases, so  a safer version,InterruptConditionis provided.Even subclasses ofCancelledErrorwill be converted to a newCancelledErrorinstance when not handled in a task, and awaited.These functions currently are only workreliablywithTaskobject implemented in Python.
Modern implementation often have a native "C" implementation ofTaskobjects and they contain inaccessible code which cannot be used by the library.  In particular, theTask.__stepmethod cannot be explicitly scheduled to the event loop.  For that reason,
a specialcreate_pytask()helper is provided to create a suitable pythonTaskinstance.However:This library does go through extra hoops to make it usable with C Tasks.
It almost works, but with two caveats:CTasks which have plainTaskStepMethWrappercallbacks scheduled cannot be interrupted.
These are typically tasks executingawait asyncio.sleep(0)or freshly created
tasks that haven't started executing.The CTask's_fut_waitingmembercannotbe cleared from our code, so there exists a time
where it can point to a valid, not-done, Future, even though the Task is about
to wake up.  This will make methods such astask_is_blocked()return incorrect
values.  Itwillget cleared when the interrupted task starts executing, however. All the more reason to usetask_interrupt()overtask_throw()since
the former allows no space for code to see the task in such an intermediate state.task_throw()deftask_throw(task:Task,exc:BaseException):passThis method will make the targetTaskimmediately runnable with the given exception
pending.If the Task was runnable due to apreviouscall totask_throw(), this will override
that call and its exception.Because of that, this method should probably not be used directly.  It is better to ensure that the
targettakes deliveryof the exception right away, because there is no way to
queue pending exceptions and they do not add up in any meaningful way.
Prefer to usetask_interrupt()below.This method willfailif the target task has a pendingcancellation, that is,
it is in the process of waking up with a pendingCancelledError.  Cancellation is
currently asynchronous, while throwing exceptions is intended to be synchronous.task_interrupt()asyncdeftask_interrupt(task:Task,exc:BaseException):passAnasyncversion oftask_throw().  When awaited,task_interrupt()is called,
followed by atask_switch()to the target.  Once awaited, the exceptionhas been raisedon the target task.By ensuring that the target task runs immediately, it is possible to reason about task
execution without having to rely on external synchronization primitives and the cooperation
of the target task.  An interrupt is neverpendingon the task (as acancellationcan
be) and therefore it cannot cause collisions with other interrupts.asyncdeftest():asyncdeftask():awaitasyncio.sleep(1)create_pytask(task)awaitasyncio.sleep(0)asserttask_is_blocked(task)awaittask_interrupt(task,InterruptException())asserttask.done()# the error has already been raised.try:awaittaskexceptCancelledError:# original error is substitutedpasselse:assertFalse,"never happens"create_pytask()Similar toasyncio.create_task()but will create a purePythonTaskwhich can safely
be used as the target fortask_throw()andtask_interrupt().  Because of implementation
issues, regularCTaskobjects, as returned byasyncio.create_task(), cannot
be interrupted in all cases, in particular when doing anawait asyncio.sleep(0)or
directly after having been created.task_timeout()This is a context manager providing a timeout functionality, similar toasyncio.timeout().
By leveragingtask_throw()and a customBaseExceptionsubclass,TimeoutInterrupt,
the logic becomes very simple and there is no unintended interaction with regular
task cancellation().