簡體   English   中英

將取消令牌傳遞給 Task.Run 似乎沒有效果

[英]Passing cancellation token to Task.Run seems to have no effect

根據thisthis ,將取消標記傳遞給任務構造函數或Task.Run將導致任務與所述標記相關聯,如果發生取消異常,則導致任務轉換為Canceled而不是Faulted

我一直在擺弄這些示例,除了阻止取消的任務啟動之外,我看不到任何好處。

更改此 MSDN 示例中的代碼

tc = Task.Run(() => DoSomeWork(i, token), token);

tc = Task.Run(() => DoSomeWork(i, token));

產生了完全相同的 output:

在此處輸入圖像描述

此代碼還會導致兩個取消的 state 任務引發相同的異常:

var token = cts.Token;

var t1 = Task.Run(() =>
{
    while (true)
    {
        Thread.Sleep(1000);
        token.ThrowIfCancellationRequested();
    };
});

var t2 = Task.Run(() =>
{
    while (true)
    {
        Thread.Sleep(1000);
        token.ThrowIfCancellationRequested();
    };
}, token);

Console.ReadKey();

try
{
    cts.Cancel();
    Task.WaitAll(t1, t2);
}
catch(Exception e)
{
    if (e is AggregateException)
    {
        foreach (var ex in (e as AggregateException).InnerExceptions)
        {
            Console.WriteLine(e.Message);
        }
    }
    else
        Console.WriteLine(e.Message);
            
}

Console.WriteLine($"without token: { t1.Status }");
Console.WriteLine($"with token: { t2.Status }");
Console.WriteLine("Done.");

在此處輸入圖像描述

顯然,從任務中拋出OperationCanceledException足以使其轉換為Canceled而不是Faulted 所以我的問題是:除了阻止取消的任務運行之外,是否有理由將令牌傳遞給任務?

Task.Run有很多重載。 由於無限while循環, t1的情況很特殊。

var t1 = Task.Run(() =>
{
    while (true)
    {
        Thread.Sleep(1000);
        token.ThrowIfCancellationRequested();
    };
});

編譯器必須在這兩個重載之間進行選擇:

public static Task Run(Action action);

public static Task Run(Func<Task> function);

...由於某些我不知道的原因,它選擇了后者。 是這個重載的實現:

public static Task Run(Func<Task?> function, CancellationToken cancellationToken)
{
    if (function == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.function);

    // Short-circuit if we are given a pre-canceled token
    if (cancellationToken.IsCancellationRequested)
        return Task.FromCanceled(cancellationToken);

    // Kick off initial Task, which will call the user-supplied function and yield a Task.
    Task<Task?> task1 = Task<Task?>.Factory.StartNew(function, cancellationToken,
        TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

    // Create a promise-style Task to be used as a proxy for the operation
    // Set lookForOce == true so that unwrap logic can be on the lookout for OCEs thrown
    // as faults from task1, to support in-delegate cancellation.
    UnwrapPromise<VoidTaskResult> promise = new UnwrapPromise<VoidTaskResult>(task1,
        lookForOce: true);

    return promise;
}

重要的細節是lookForOce: true 讓我們看看UnwrapPromise class 內部

// "Should we check for OperationCanceledExceptions on the outer task and interpret them
// as proxy cancellation?"
// Unwrap() sets this to false, Run() sets it to true.
private readonly bool _lookForOce;

..在下面的另一點

case TaskStatus.Faulted:
    List<ExceptionDispatchInfo> edis = task.GetExceptionDispatchInfos();
    ExceptionDispatchInfo oceEdi;
    if (lookForOce && edis.Count > 0 &&
        (oceEdi = edis[0]) != null &&
        oceEdi.SourceException is OperationCanceledException oce)
    {
        result = TrySetCanceled(oce.CancellationToken, oceEdi);
    }
    else
    {
        result = TrySetException(edis);
    }
    break;

因此,雖然內部創建的Task<Task?> task1 task1 以Faulted state 結束,但其解包后的版本以Canceled結束,因為異常的類型是OperationCanceledException (代碼中縮寫為oce )。

在 TPL 的歷史上,這是一段相當曲折的旅程,在不同的時間和框架中引入了方法,以服務於不同的目的。 最終結果是有點不一致,或者如果你願意這么說的話,會有細微的行為。 您可能會感興趣的相關文章是:Stephen Toub 的Task.Run 與 Task.Factory.StartNew

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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