开源AwaitableCompletionSource,用于取代TaskCompletionSource

1 TaskCompletionSource介绍

TaskCompletionSource提供创建未绑定到委托的任务,任务的状态由TaskCompletionSource上的方法显式控制,以支持未来的操作传播到它创建的任务。

使场景

EAP(基于事件的异步模式)转TAP(基于任务的异步模式)

public static Task<string> DownloadStringAsync(Uri url) { var tcs = new TaskCompletionSource<string>(); var wc = new WebClient(); wc.DownloadStringCompleted += (s,e) => { if (e.Error != null) tcs.TrySetException(e.Error); else if (e.Cancelled) tcs.TrySetCanceled(); else tcs.TrySetResult(e.Result); }; wc.DownloadStringAsync(url); return tcs.Task; }

结合CancellationTokenSource实现超时任务

public static async Task<string> DownloadStringAsync(Uri url, TimeSpan timeout) { var tcs = new TaskCompletionSource<string>(); var wc = new WebClient(); wc.DownloadStringCompleted += (s, e) => { if (e.Error != null) tcs.TrySetException(e.Error); else if (e.Cancelled) tcs.TrySetCanceled(); else tcs.TrySetResult(e.Result); }; using var cts = new CancellationTokenSource(); cts.Token.Register(() => tcs.TrySetException(new TimeoutException()), useSynchronizationContext: false); cts.CancelAfter(timeout); wc.DownloadStringAsync(url); return await tcs.Task; } 不足之处

一个实例只支持创建一次任务

一个TaskCompletionSource<>实例,给它的任务设置结果或异常之后,这个实例就没有什么了,既无法重置,也无法再创建新的任务实例。在高密集创建TaskCompletionSource<>要求的场景里,这可能给GC带来一点压力。

没有原生支持延时设置异常或结果功能

在网络请求里或更多场景里,可能会收不到或在特定的时间内收不到响应事件,这时TaskCompletionSource<>不得不和CancellationTokenSource结合使用,加上计时器完成超时功能,又多得创建一个对象实例。

2 AwaitableCompletionSource介绍

AwaitableCompletionSource的灵感来源于asp.netcore的kestrel的SocketAwaitableEventArgs,它把SocketAsyncEventArgs改装成支持单例可重复await的功能。AwaitableCompletionSource也支持单例可重复await,同时使用过后不再使用的实例还支持dispose回收到池中。

支持Singleton,单个实例持续使用;

支持Dispose后回收复用,创建实例0分配;

支持超时自动设置结果或异常,性能远好于TaskCompletionSource包装增加超时功能;

如何使用

使用方式与TaskCompletionSource大体一致。但是要使用静态类Create来创建实例,使用完成后Dispose实例。

var source = AwaitableCompletionSource.Create<string>(); ThreadPool.QueueUserWorkItem(s => ((IAwaitableCompletionSource)s).TrySetResult("1"), source); Console.WriteLine(await source.Task); // 支持多次设置获取结果 source.TrySetResultAfter("2", TimeSpan.FromSeconds(1d)); Console.WriteLine(await source.Task); // 支持多次设置获取结果 source.TrySetResultAfter("3", TimeSpan.FromSeconds(2d)); Console.WriteLine(await source.Task); // 实例使用完成之后,可以进行回收复用 source.Dispose(); 3 性能比较 瞬态实例和调用TrySetResult

在频繁创建与回收AwaitableCompletionSource的场景,对于SetResult的使用,AwaitableCompletionSource的cpu时间明显高于TaskCompletionSource,但内存分配为0。

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
TaskCompletionSource_SetResult   39.92 ns   0.201 ns   0.179 ns   0.0229   -   -   96 B  
AwaitableCompletionSource_SetResult   86.19 ns   0.315 ns   0.295 ns   -   -   -   -  
单例和调用TrySetResult

单例AwaitableCompletionSource的场景,对于SetResult的使用,AwaitableCompletionSource与TaskCompletionSource的cpu时间相当,内存分配为0。

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
TaskCompletionSource_SetResult   41.46 ns   0.744 ns   1.180 ns   0.0229   -   -   96 B  
AwaitableCompletionSource_SetResult   49.30 ns   0.528 ns   0.494 ns   -   -   -   -  
瞬态实例和超时等待

注: TaskCompletionSource<>结合CancellationTokenSource<>实现超时。

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
TaskCompletionSource_WithTimeout   237.0 ns   4.76 ns   5.85 ns   0.1357   -   -   568 B  
AwaitableCompletionSource_WithTimeout   176.6 ns   0.83 ns   0.74 ns   -   -   -   -  
单例超时等待

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppgxf.html