可移植的C库,支持易于使用的异步事件循环和协同程序

2020-07-12 06:36:30

灵感来自python中的协议线程、async.h、coroutines.h和异步/等待。这是基于Duff设备的C的异步/等待/法等待/事件循环实现。

它为每个状态使用96字节的内存,但为您提供了无缝嵌套能力、错误处理和堆栈管理。

它比其他实现更容易理解,因为异步状态/堆栈管理完全由lib处理。

您不能跨函数调用保留局部变量,但库提供了一种持久存储它们的方法(请参阅实践)。

使用async_new创建新的coro时,类型表示为空函数堆栈(局部变量),对于char,则为tydeff。

阻塞并运行事件循环,直到main_coro完成。可以调用任意次以保留未完成的任务状态,如果退出后没有获得所有权,则释放main_coro。

在不阻塞当前进度的情况下将任务添加到事件循环,返回NULL并在失败时释放CORO。

在不阻塞当前进度的情况下将n个任务从状态数组添加到事件循环中,如果其中一个CORO为NULL或如果没有足够的内存添加它们,则不执行任何操作并返回NULL

将任务添加到事件循环并阻止进度,直到coro执行完毕。如果取消了CORO任务,则设置ASSYNC_ERRNO:ASSYNC_ECANCELED;如果没有足够的内存来创建任务,则设置ASYNC_ENOMEM并释放CORO;否则设置任何自定义错误代码,否则设置为ASYNC_OK。仅当CORO设置ASSYNC_ERRNO时,花括号内的代码才会执行,ASSYNC_ERRNO==ASYNC_OK上的花括号将被忽略。

从Function(Function必须遵循AsyncCallback签名)返回一个新的CORO,其参数和堆栈内存能够保存传递给T_LOCALS的类型:INT、STRUT、ARRAY、CUSTOM TYPE或ASYNC_NONE(如果根本不需要堆栈内存。

将几个CORO集合在一起,将它们并行运行成单个CORO,然后将其返回。如果Gather()被取消,则所有提交的CORO(尚未完成)也将被取消。

Async_Gather的各种版本,期望直接传递COROS(不需要在失败时清除它们)。

块执行延迟秒,精度因平台而异,但您可以在需要时使用不可移植的计时器轻松提供自己的实现。

等待CORO完成并超时。否则取消,并将ASSYNC_ERRO设置为ASYNC_ECANCELED。

使用async_alloc分配的空闲PTR无需等待事件循环为您执行此操作。在长时间运行的任务中可能很有用,但无论如何,带取消功能的malloc更可取,速度也更快。

如果内存块已成功排队并将在稍后由事件循环释放,则返回TRUE

取消当前的CORO。请注意,如果coro操作一些未使用async_alloc分配的自定义内存,则会导致内存泄漏。

为当前异步函数设置取消时要调用的取消函数。CANCEL_FUNC必须跟在AsyncCancelCallback类型签名之后。

也可以指定展开为当前异步函数的Async_Err类型的值的宏。

在手动处理状态或创建自定义事件循环之前,不应手动使用coro的空闲内存,忽略NULL。

未来的一些API方法可能会使用su_state作为输入coro类型,明确表示它窃取了当前所有权,在这种情况下,用户必须访问传递给s_astate对象的权限,或者在将所有权转移给该方法之前再次手动执行INCREF所有权。

将coro标记为当前正在使用的";,这样它就不会被自动删除。在Async Function/Cancel Function";中,后面必须跟ASYNC_DECREF。

将coro标记为不再使用。请注意,如果其他函数也在此coro上使用INCREF,则不会立即删除它。

准备适配器功能,不为ARG分配内存,以防分配错误时转到err_label;

使用ASYNC_NEW创建新协程时,将指向值的指针作为参数传递。然后赋值给函数体内的指针:

Async f(State){async_egin(State);int*res=state->;args;/*args这里是指向int a的指针(可以是指向任何c对象的指针)*/*res=42;.}.。int a;fawait(async_new(f,&;a,ASYNC_NONE)){printf(";错误%s发生\n";,async_strerror(Async_Errno));}。

如果我们想要多个不同类型的返回值,我们可以用例如struct*替换int*。任何类型指针都可以。

只需创建一个包含所有所需变量的struct,然后在创建新coro时将struct作为类型传递。请参见下面的examples/example.c和简短示例。

#include<;stdio.h>;#include";async2.h";struct amain_stack{int i;};async amain(S_Astate State){struct amain_stack*locals=state->;locals=state->;args;async_egin(State);/*注意,本地变量->;i赋值为0是在异步内部进行的。*不要将它们分配到外部!*/for(locals->;i=0;locals->;i<;3;locals->;i++){printf(";TASK%s:Iteration№%d\n&34;,args,locals->;i+1);/*让其他任务正常运行。与python类似,等待asyncio.sleep(0)使用。*/异步屈服;}异步结束;}int main(Void){struct async_event_loop*loop=async_get_event_loop();loop->;init();async_create_task(async_new(amain,";task 1";,struct amain_stack));/*分配足够的内存来保存类型为struct amain_stack*/async_create_task的本地变量。,struct amain_stack));Loop->;Run_Foreve();Loop->;Destroy();}。

faWait(Async_Func(10)){if(Async_errno==ASYNC_ENOMEM){/*处理内存错误.。*/}}否则{/*协程已完成,没有错误*/}。

本地变量->;task=Async_CREATE_TASK(coro());ASYNC_XINCREF(本地变量->;task);/*获取任务的所有权,使其不会在完成后立即删除*//*执行.。*/IF(LOCALS-&>TASK){AWAIT(Async_DONE(LOCALS-&>TASK));IF(LOCALS-&gT;TASK;ERR!=ASYNC_OK){/*手动处理任务错误*/}}ASYNC_XDECREF(LOCALS-&>TASK);

总是用Asyncio_Slear或Asyncio_Wait_For这样的方式包装协程,这样它们的使用就会非常简单(在它们准备时不需要在它们上使用Async_new),这样您就可以提供可以接受任何参数的自定义异步函数,但是返回类型仍将被限制为s_astate。ASSYNC_PREPARE和ASYNC_PREPARE_NOARGS对于这样的函数包装非常有帮助。

使用基础dev方法如async_new_coro_和async_alloc_,以克服创建异步函数时的用户空间限制。

始终使用所有权包装所有使用的任务,否则有可能获得无效的指针访问。记住:一个INCREF-一个DECREF。

为您的友好方法提供取消功能,这样即使取消的功能也不会破坏所有权和CORO将被正确删除。

与协议线程一样,您必须非常小心地使用异步子例程中的switch语句和手动创建的局部变量(未存储在局部变量中的变量)。一般来说,最好避开它们。

与协议线程一样,您不能进行阻塞系统调用并保留异步语义。必须将这些更改为符合条件的非阻塞调用。

您不能在等待期间使用setjump和long jump函数,因为async2改变了通常的函数流,所以给定的代码很好:

这意味着您无法使用像Exceptions4c这样的C异常库来包装faWait或await或产生调用,而应使用async_errno和faWait花括号。