[英]Use Task-based Asynchronous Pattern (TAP) with .NET 4.0 and C# 4.0
[英]Timeout pattern on task-based asynchronous method in C#
據我所知,有兩種可能的模式可以實現基於任務的異步方法的超時:
public Task DoStuffAsync(TimeSpan timeout)
這種方法更難實現,因為要為整個調用堆棧實現全局超時並不容易。 例如,Web API 控制器接收到一個 HTTP 請求並調用DoStuffAsync
,並且調用者想要 3 秒的全局超時。
也就是說,每個內部異步方法調用都需要接收已使用時間的減法...
public Task DoStuffAsync(CancellationToken cancellationToken)
..........
CancellationTokenSource cancellationSource = new CancellationTokenSource();
Task timeoutTask = Task.Delay(3000);
if(await Task.WhenAny(DoStuffAsync(cancellationTokenSource), timeoutTask) == timeoutTask)
{
cancellationSource.Cancel();
throw new TimeoutException();
}
這似乎是最可靠和最容易實現的模式。 第一個調用者定義了一個全局超時,如果超時,將取消所有掛起的操作。 此外,它為直接調用者提供取消令牌,內部調用將共享相同的取消令牌引用。 因此,如果頂級調用者超時,它將能夠取消任何工作線程。
是否有任何我遺漏的模式,或者,如果我使用無內置超時開發 API,我的方式是否正確?
雖然您可以將WithCancellation
用於取消和超時,但我認為這對於您的需求來說太過分了。
async
操作超時的一個更簡單、更清晰的解決方案是使用Task.WhenAny
await
實際操作和超時任務。 如果超時任務首先完成,您就會超時。 否則,操作成功完成:
public static async Task<TResult> WithTimeout<TResult>(this Task<TResult> task, TimeSpan timeout)
{
if (task == await Task.WhenAny(task, Task.Delay(timeout)))
{
return await task;
}
throw new TimeoutException();
}
用法:
try
{
await DoStuffAsync().WithTimeout(TimeSpan.FromSeconds(5));
}
catch (TimeoutException)
{
// Handle timeout.
}
如果您不想拋出異常(就像我一樣),那就更簡單了,只需返回默認值:
public static Task<TResult> WithTimeout<TResult>(this Task<TResult> task, TimeSpan timeout)
{
var timeoutTask = Task.Delay(timeout).ContinueWith(_ => default(TResult), TaskContinuationOptions.ExecuteSynchronously);
return Task.WhenAny(task, timeoutTask).Unwrap();
}
是否有任何我遺漏的模式,或者,如果我使用無內置超時開發 API,我的方式是否正確?
免責聲明:
當我們談論處於取消狀態的Task
時,我們的意思是我們在操作進行時取消操作。 當我們談論取消時,這可能不是這種情況,因為如果任務在指定的時間間隔后完成,我們就會簡單地丟棄它。 這在下面的 Stephan Toubs 文章中討論了為什么 BCL 不提供取消正在進行的操作的 OOTB 功能。
我現在看到的常見方法是無內置方法,我發現自己主要使用的方法是實現取消機制。 這絕對是兩者中更容易的,讓最高幀負責取消,同時將取消令牌傳遞給內部幀。 如果您發現自己在重復此模式,則可以使用已知的WithCancellation
擴展方法:
public static async Task<T> WithCancellation<T>(
this Task<T> task, CancellationToken cancellationToken)
{
var cancellationCompletionSource = new TaskCompletionSource<bool>();
using (cancellationToken.Register(() => cancellationCompletionSource.TrySetResult(true)))
{
if (task != await Task.WhenAny(task, cancellationCompletionSource.Task))
{
throw new OperationCanceledException(cancellationToken);
}
}
return await task;
}
這是來自 Stephen Toub 的如何取消不可取消的異步操作? 這並不完全符合您的要求,但絕對值得一讀。
任務取消文檔繼續指定兩種任務取消方式:
您可以使用以下選項之一終止操作:
通過簡單地從委托返回。 在許多情況下,這已經足夠了; 但是,以這種方式取消的任務實例會轉換到 TaskStatus.RanToCompletion 狀態,而不是 TaskStatus.Canceled 狀態。
通過拋出 OperationCanceledException 並將請求取消的令牌傳遞給它。 執行此操作的首選方法是使用 ThrowIfCancellationRequested 方法。 以這種方式取消的任務轉換為 Canceled 狀態,調用代碼可以使用該狀態來驗證任務是否響應了其取消請求
編輯
至於您擔心使用TimeSpan
來指定所需的間隔,請使用帶有TimeSpan
參數的CancellationTokenSource
構造函數的重載:
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
var task = Task.Run(() => DoStuff()).WithCancellation(cts.Token);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.