Package: unsync
unsyncUnsynchronizeasyncioby using an ambient event loop, or executing in separate threads or processes.Quick OverviewFunctions marked with the@unsyncdecorator will behave in one of the following ways:asyncfunctions will run in theunsync.loopevent loop executed fromunsync.threadRegular functions will execute inunsync.thread_executor, aThreadPoolExecutorUseful for IO bounded work that does not supportasyncioRegular functions marked with@unsync(cpu_bound=True)will execute inunsync.process_executor, aProcessPoolExecutorUseful for CPU bounded workAll@unsyncfunctions will return anUnfutureobject.
This new future type combines the behavior ofasyncio.Futureandconcurrent.Futurewith the following changes:Unfuture.set_resultis threadsafe unlikeasyncio.FutureUnfutureinstances can be awaited, even if made fromconcurrent.FutureUnfuture.result()is a blocking operationexceptinunsync.loop/unsync.threadwhere
it behaves likeasyncio.Future.resultand will throw an exception if the future is not doneExamplesSimple SleepA simple sleeping example withasyncio:asyncdefsync_async():awaitasyncio.sleep(1)return'I hate event loops'asyncdefmain():future1=asyncio.create_task(sync_async())future2=asyncio.create_task(sync_async())awaitfuture1,future2print(future1.result()+future2.result())asyncio.run(main())# Takes 1 second to runSame example withunsync:@unsyncasyncdefunsync_async():awaitasyncio.sleep(1)return'I like decorators'unfuture1=unsync_async()unfuture2=unsync_async()print(unfuture1.result()+unfuture2.result())# Takes 1 second to runMulti-threading an IO-bound functionSynchronous functions can be made to run asynchronously by executing them in aconcurrent.ThreadPoolExecutor.
This can be easily accomplished by marking the regular function@unsync.@unsyncdefnon_async_function(seconds):time.sleep(seconds)return'Run concurrently!'start=time.time()tasks=[non_async_function(0.1)for_inrange(10)]print([task.result()fortaskintasks])print('Executed in{}seconds'.format(time.time()-start))Which prints:['Run concurrently!', 'Run concurrently!', ...]
Executed in 0.10807514190673828 secondsContinuationsUsingUnfuture.thenchains asynchronous calls and returns anUnfuturethat wraps both the source, and continuation.
The continuation is invoked with the source Unfuture as the first argument.
Continuations can be regular functions (which will execute synchronously), or@unsyncfunctions.@unsyncasyncdefinitiate(request):awaitasyncio.sleep(0.1)returnrequest+1@unsyncasyncdefprocess(task):awaitasyncio.sleep(0.1)returntask.result()*2start=time.time()print(initiate(3).then(process).result())print('Executed in{}seconds'.format(time.time()-start))Which prints:8
Executed in 0.20314741134643555 secondsMixing methodsWe'll start by converting a regular synchronous function into a threadedUnfuturewhich will begin our request.@unsyncdefnon_async_function(num):time.sleep(0.1)returnnum,num+1We may want to refine the result in another function, so we define the following continuation.@unsyncasyncdefresult_continuation(task):awaitasyncio.sleep(0.1)num,res=task.result()returnnum,res*2We then aggregate all the results into a single dictionary in an async function.@unsyncasyncdefresult_processor(tasks):output={}fortaskintasks:num,res=awaittaskoutput[num]=resreturnoutputExecuting the full chain ofnon_async_function→result_continuation→result_processorwould look like:start=time.time()print(result_processor([non_async_function(i).then(result_continuation)foriinrange(10)]).result())print('Executed in{}seconds'.format(time.time()-start))Which prints:{0: 2, 1: 4, 2: 6, 3: 8, 4: 10, 5: 12, 6: 14, 7: 16, 8: 18, 9: 20}
Executed in 0.22115683555603027 secondsPreserving typingAs far as we know it is not possible to change the return type of a method or function using a decorator.
Therefore, we need a workaround to properly use IntelliSense. You have three options in general:Ignore type warnings.Use a suppression statement where you reach the type warning.A. When defining the unsynced method by changing the return type to anUnfuture.B. When using the unsynced method.Wrap the function without a decorator. Example:deffunction_name(x:str)->Unfuture[str]:async_method=unsync(__function_name_synced)returnasync_method(x)def__function_name_synced(x:str)->str:returnx+'a'future_result=function_name('b')self.assertEqual('ba',future_result.result())Custom Event LoopsIn order to use custom event loops, be sure to set the event loop policy before calling any@unsyncmethods.
For example, to useuvloopsimply:importunsyncimportuvloop@unsyncasyncdefmain():# Main entry-point....uvloop.install()# Equivalent to asyncio.set_event_loop_policy(EventLoopPolicy())main()