【问题标题】:Is there a proper way to cancel an asynchronous task?是否有适当的方法来取消异步任务?
【发布时间】:2017-08-29 18:49:47
【问题描述】:

我遇到了如何正确取消异步任务的问题。

这是一些草稿。

我的入口点运行两个异步任务。第一个任务做了一些“长时间”的工作,第二个任务取消了它。

入口点:

private static void Main()
{
    var ctc = new CancellationTokenSource();

    var cancellable = ExecuteLongCancellableMethod(ctc.Token);

    var cancelationTask = Task.Run(() =>
    {
        Thread.Sleep(2000);

        Console.WriteLine("[Before cancellation]");

        ctc.Cancel();
    });

    try
    {
        Task.WaitAll(cancellable, cancelationTask);
    }
    catch (Exception e)
    {
        Console.WriteLine($"An exception occurred with type {e.GetType().Name}");
    }
}

返回可取消任务的方法:

private static Task ExecuteLongCancellableMethod(CancellationToken token)
{
    return Task.Run(() =>
    {
        token.ThrowIfCancellationRequested();

        Console.WriteLine("1st"); 
        Thread.Sleep(1000);

        Console.WriteLine("2nd");
        Thread.Sleep(1000);

        Console.WriteLine("3rd");
        Thread.Sleep(1000);

        Console.WriteLine("4th");
        Thread.Sleep(1000);

        Console.WriteLine("[Completed]");

    }, token);  
}   

我的目的是在调用取消后立即停止写入“1st”、“2nd”、“3rd”。 但我得到以下结果:

1st
2nd
3rd
[Before cancellation]
4th
[Completed]

出于显而易见的原因,我没有收到请求取消时抛出的异常。所以我尝试重写方法如下:

private static Task ExecuteLongCancellableAdvancedMethod(CancellationToken token)
{
    return Task.Run(() =>
    {
        var actions = new List<Action>
        {
            () => Console.WriteLine("1st"),
            () => Console.WriteLine("2nd"),
            () => Console.WriteLine("3rd"),
            () => Console.WriteLine("4th"),
            () => Console.WriteLine("[Completed]")
        };

        foreach (var action in actions)
        {
            token.ThrowIfCancellationRequested();

            action.Invoke();

            Thread.Sleep(1000);
        }

    }, token);
}

现在我得到了我想要的:

1st
2nd
[Before cancellation]
3rd
An exception occurred with type AggregateException

但我猜想创建一组 Action 委托并循环遍历它并不是解决我的问题的最方便的方法。

那么正确的做法是什么?为什么我需要将取消令牌作为第二个参数传递给 Task.Run 方法?

【问题讨论】:

  • 如果已经请求取消令牌,Task.Run 根本不会运行委托
  • 您需要在您希望执行实际可取消的每个地方显式调用token.ThrowIfCancellationRequested();。所以在你的第一个例子中,在每个Console.WriteLine之前。
  • 我强烈建议您也阅读 ContinueWith 方法,因为您询问了“正确”方法,我认为您需要熟悉 ContinueWith 和 TaskContinuationOptions.NotOnCanceled。这是一个包含更多有用信息的链接:social.msdn.microsoft.com/Forums/en-US/…

标签: c# .net asynchronous task cancellationtokensource


【解决方案1】:

Task 不会自行取消,由您自己来检测取消请求并彻底中止您的工作。这就是token.ThrowIfCancellationRequested(); 所做的。

您应该将这些检查放在整个代码中,在可以完全停止执行或回滚到安全状态的地方。

在您的第二个示例中,您在每次循环迭代时调用它一次,它工作正常。第一个例子一开始只调用一次。如果此时令牌尚未取消,则任务将运行完成,就像您看到的那样。

如果你把它改成这样,你也会看到你期望的结果。

return Task.Run(() =>
{
    token.ThrowIfCancellationRequested();
    Console.WriteLine("1st"); 
    Thread.Sleep(1000);

    token.ThrowIfCancellationRequested();
    Console.WriteLine("2nd");
    Thread.Sleep(1000);

    token.ThrowIfCancellationRequested();
    Console.WriteLine("3rd");
    Thread.Sleep(1000);

    token.ThrowIfCancellationRequested();
    Console.WriteLine("4th");
    Thread.Sleep(1000);

    Console.WriteLine("[Completed]");

}, token); 

【讨论】:

  • 谢谢,我知道我写的代码是如何工作的,但是有没有更方便的方法来取消任务?我的意思是在每个逻辑块之后写“ThrowIfCancellationRequested”让代码对我来说看起来很糟糕
  • 不,我不知道。只有开发人员才能知道代码中的安全位置在哪里,以彻底取消TaskTask 中的代码可能会使变量和数据处于不干净、未知的状态,如果它在请求取消时以某种方式取消,以及that could be just as dangerous as Thread.Abort。试想一下,如果Task 在哪里对一个对象进行锁定,并且在它可以释放之前被取消,或者如果任务正在处理非托管内存
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多