将 CancellationToken 作为参数传递给 Task.Run 有什么好处?

Whats the benefit of passing a CancellationToken as a parameter to Task.Run?

显然我意识到它使我能够取消任务,但是这段代码实现了相同的效果而无需将令牌传递给 Task.Run

实际区别是什么?谢谢

Dim cts As New CancellationTokenSource
Dim ct As CancellationToken = cts.Token
Task.Run(Sub()
             For i = 1 To 1000
                 Debug.WriteLine(i)
                 ct.ThrowIfCancellationRequested()
                 Threading.Thread.Sleep(10)
             Next
         End Sub)

cts.CancelAfter(500)

VS

Dim cts As New CancellationTokenSource
Dim ct As CancellationToken = cts.Token
Task.Run(Sub()
             For i = 1 To 1000
                 Debug.WriteLine(i)
                 ct.ThrowIfCancellationRequested()
                 Threading.Thread.Sleep(10)
             Next
         End Sub, ct)

cts.CancelAfter(500)

API docs for Task.Run(Action, CancellationToken)有这样一句话:

If cancellation is requested before the task begins execution, the task does not execute. Instead it is set to the Canceled state and throws a TaskCanceledException exception.

所以在您的场景中,没有任何实际差异,因为您在发出取消之前等待了 500 毫秒。在那段时间内,任务被安排,开始执行,并且 运行s 在发出取消之前通过循环多次,表现为从 ct.ThrowIfCancellationRequested().

抛出的异常

Task.Run(Action)Task.Run(Action, CancellationToken) 之间的区别在您的示例的这个修改版本中更加明显:

Try
    Dim cts As New CancellationTokenSource
    Dim ct As CancellationToken = cts.Token

    cts.Cancel()

    Dim task As Task = Task.Run(
        Sub()
            Console.WriteLine("Started running your code!")
            ct.ThrowIfCancellationRequested()
            Console.WriteLine("Finished running your code!")
        End Sub, ct)

    task.Wait()

Catch ex As AggregateException
    Console.Error.WriteLine("Caught exception: " & ex.InnerException.Message)
End Try

Console.WriteLine("Done, press Enter to quit.")
Console.ReadLine()

在这种情况下,Task.Run 将任务安排到 运行,但还将取消标记与该任务相关联。当我们调用 task.Wait() 时,线程池在执行任务之前,会检查取消令牌并注意到已在该令牌上发出取消,因此它决定在执行任务之前取消。所以输出是:

Caught exception: A task was canceled.
Done, press Enter to quit.

如果您将:End Sub, ct) 替换为 End Sub),则线程池不知道取消令牌,因此即使您发出了取消,它也会继续执行任务,在您的任务代码本身检查取消之前。所以输出是:

Started running your code!
Caught exception: The operation was canceled.
Done, press Enter to quit.

(您可以看到这两种情况下的异常消息也略有不同。)

总而言之,向Task.Run方法提供取消令牌允许线程池本身在线程池有机会执行任务之前知道任务是否被取消。这允许线程池通过甚至不用启动任务来节省时间和资源。运行宁任务。

实际区别在于如果令牌被取消,Task 将处于什么状态。

抱歉这里的 C# 代码...

var cts = new CancellationTokenSource();
var withToken = Task.Run(Callback, cts.Token);
var withoutToken = Task.Run(Callback);
cts.Cancel();
void Callback()
{
    Thread.Sleep(1000);
    throw new OperationCanceledException(cts.Token);
}

try
{
    Task.WaitAll(withToken, withoutToken);
}
catch
{
}

Console.WriteLine($"withToken.IsCanceled:    {withToken.IsCanceled}");
Console.WriteLine($"withToken.IsFaulted:     {withToken.IsFaulted}");
Console.WriteLine($"withToken.Status:        {withToken.Status}");
Console.WriteLine();
Console.WriteLine($"withoutToken.IsCanceled: {withoutToken.IsCanceled}");
Console.WriteLine($"withoutToken.IsFaulted:  {withoutToken.IsFaulted}");
Console.WriteLine($"withoutToken.Status:     {withoutToken.Status}");

该代码打印:

withToken.IsCanceled:    True
withToken.IsFaulted:     False
withToken.Status:        Canceled

withoutToken.IsCanceled: False
withoutToken.IsFaulted:  True
withoutToken.Status:     Faulted

这里的想法是,如果传递给 Task.Run 的回调抛出 OperationCanceledException(或派生类型),则结果 Task 将被标记为"Faulted" 除非异常的 CancellationToken 等于您传入的令牌(CancellationToken 在您抛出异常时被取消) .