簡體   English   中英

C#中基於任務的異步方法的超時模式

[英]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 的如何取消不可取消的異步操作? 這並不完全符合您的要求,但絕對值得一讀。

任務取消文檔繼續指定兩種任務取消方式:

您可以使用以下選項之一終止操作:

  1. 通過簡單地從委托返回。 在許多情況下,這已經足夠了; 但是,以這種方式取消的任務實例會轉換到 TaskStatus.RanToCompletion 狀態,而不是 TaskStatus.Canceled 狀態。

  2. 通過拋出 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM