CancellationToken.ThrowIfCancellationRequested之后的Faulted vs Cancelled任务状态

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (1)
  • 关注 (0)
  • 查看 (324)

下面的代码开始并等待两个任务,task1task2几乎完全相同。task1不同于task2的是它运行一个永无止境的循环。这两种情况对于执行CPU绑定工作的一些现实生活场景都非常典型。

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication
{
    public class Program
    {
        static async Task TestAsync()
        {
            var ct = new CancellationTokenSource(millisecondsDelay: 1000);
            var token = ct.Token;

            // start task1
            var task1 = Task.Run(() =>
            {
                for (var i = 0; ; i++)
                {
                    Thread.Sleep(i); // simulate work item #i
                    token.ThrowIfCancellationRequested();
                }
            });

            // start task2
            var task2 = Task.Run(() =>
            {
                for (var i = 0; i < 1000; i++)
                {
                    Thread.Sleep(i); // simulate work item #i
                    token.ThrowIfCancellationRequested();
                }
            });  

            // await task1
            try
            {
                await task1;
            }
            catch (Exception ex)
            {
                Console.WriteLine(new { task = "task1", ex.Message, task1.Status });
            }

            // await task2
            try
            {
                await task2;
            }
            catch (Exception ex)
            {
                Console.WriteLine(new { task = "task2", ex.Message, task2.Status });
            }
        }

        public static void Main(string[] args)
        {
            TestAsync().Wait();
            Console.WriteLine("Enter to exit...");
            Console.ReadLine();
        }
    }
}

输出:

{ task = task1, Message = The operation was canceled., Status = Canceled }
{ task = task2, Message = The operation was canceled., Status = Faulted }

为什么状态task1Cancelled,但状态task2Faulted请注意,在这两种情况下,我都不会将其token作为第二个参数传递给Task.Run

提问于
用户回答回答于

这里有两个问题。首先,传递CancellationTokenTask.RunAPI 总是一个好主意,除了可用于任务的lambda。这样做会将令牌与任务关联起来,对于正确传播由所触发的取消操作至关重要token.ThrowIfCancellationRequested

然而,这并不能解释为什么取消状态task1仍然正确地传播(task1.Status == TaskStatus.Canceled),而不是task2task2.Status == TaskStatus.Faulted)。

现在,这可能是其中巧妙的C#类型推理逻辑可能违背开发者意愿的极少数情况之一。这里这里详细讨论。总而言之,在task1以下情况下,Task.Run编译器推断以下重写:

public static Task Run(Func<Task> function)

而不是:

public static Task Run(Action action)

这是因为task1lambda没有自然的代码路径for,所以它可能是一个Func<Task>lambda,尽管它不是async,它不会返回任何东西。这是编译器更喜欢的选项Action。然后,这种覆盖的使用Task.Run等同于:

var task1 = Task.Factory.StartNew(new Func<Task>(() =>
{
    for (var i = 0; ; i++)
    {
        Thread.Sleep(i); // simulate work item #i
        token.ThrowIfCancellationRequested();
    }
})).Unwrap();

最后,这可能是一个理想的行为task1(当然不是task2),但我们不希望在任何情况下在场景后面创建嵌套任务。而且,task1通过breakfor循环中引入一个条件,这种行为很容易被破坏。

正确的代码task1应该是这样的

var task1 = Task.Run(new Action(() =>
{
    for (var i = 0; ; i++)
    {
        Thread.Sleep(i); // simulate work item #i
        token.ThrowIfCancellationRequested();
    }
}), token);

所属标签

可能回答问题的人

  • 西风

    renzha.net · 站长 (已认证)

    9 粉丝1 提问5 回答
  • uncle_light

    5 粉丝518 提问3 回答
  • 学生

    2 粉丝477 提问3 回答
  • 军哥

    重庆雷驰信息技术有限公司 · 经理 (已认证)

    5 粉丝1 提问3 回答

扫码关注云+社区

领取腾讯云代金券