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

时间:2018-01-18 01:33:09

标签: .net vb.net task task-parallel-library cancellation

显然我意识到它可以让我取消任务,但是这段代码可以实现相同的效果而无需将令牌传递给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)

2 个答案:

答案 0 :(得分:4)

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

  

如果在任务开始执行之前请求取消,则该任务不会执行。而是将其设置为Canceled状态并抛出TaskCanceledException异常。

因此,在您的方案中,没有任何实际差异,因为您在发出取消之前等待500毫秒。在此期间,任务被安排,开始执行,并在发布取消之前多次遍历循环,表现为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方法提供取消令牌允许线程池本身在线程池有机会执行任务之前知道任务是否被取消。这样,线程池就可以节省时间和资源,甚至不用费心去开始运行任务。

答案 1 :(得分:-1)

实际的区别在于,如果令牌被取消,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

这里的想法是,如果传递给OperationCanceledException的回调引发了Task.Run(或派生类型),则结果Task将被标记为“错误“,除非异常的CancellationToken等于您传递的令牌( 并且CancellationToken在引发异常时被取消)。