【问题标题】:Whats the benefit of passing a CancellationToken as a parameter to Task.Run?将 CancellationToken 作为参数传递给 Task.Run 有什么好处?
【发布时间】:2018-06-27 00:13:02
【问题描述】:

显然我意识到它使我能够取消任务,但是这段代码无需将令牌传递到 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)

【问题讨论】:

  • 在这种情况下,它不会有太大的不同,但很可能Task 的上下文无法访问令牌分配给的原始变量。例如,如果对象 A 创建了一个执行对象 B 的方法的 Task,B 可能不知道 A 的存在,那么它如何访问仅分配给 A 中的局部变量的 CancellationToken?跨度>
  • @jmcilhinney 好点,我也在想同样的事情。在非平凡的用例中更加通用。顺便说一句,你回答了我的很多问题,非常感谢你的帮助 jmcilhinney

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


【解决方案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 将被标记为“故障”,除非异常是 @987654327 @ 等于您传入的令牌(并且 CancellationToken 在您抛出异常时被取消)。

【讨论】:

    【解决方案2】:

    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 方法提供取消标记允许线程池本身在线程池有机会执行任务之前知道任务是否被取消。这使得线程池可以节省时间和资源,甚至不必费心开始运行任务。

    【讨论】:

      猜你喜欢
      • 2023-03-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-06
      • 2017-03-23
      • 1970-01-01
      • 2014-02-22
      相关资源
      最近更新 更多