[英]Passing cancellation token to Task.Run seems to have no effect
根據this和this ,將取消標記傳遞給任務構造函數或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.