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 | - | - | - | - |
單例超時等待
注:AwaitableCompletionSource單例,TaskCompletionSource瞬態。
Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|
TaskCompletionSource_WithTimeout | 233.1 ns | 4.59 ns | 6.58 ns | 0.1357 | - | - | 568 B |
AwaitableCompletionSource_WithTimeout | 131.5 ns | 1.41 ns | 1.32 ns | - | - | - | - |
4 總結
AwaitableCompletionSource在多個場景下可替代TaskCompletionSource,專案我已開源在https://github.com/xljiulang/AwaitableCompletionSource