故障VS CancellationToken.ThrowIfCancellationRequested后取消任务状态

编程入门 行业动态 更新时间:2024-10-26 18:18:20
本文介绍了故障VS CancellationToken.ThrowIfCancellationRequested后取消任务状态的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

通常我不张贴的答案的问题,但这次我想吸引一些注意些什么,我认为可能是一个不起眼但常见的问题。它是由这个问题触发,从那以后,我检讨我自己的老code,发现有些是受这也。

Usually I don't post a question with the answer, but this time I'd like to attract some attention to what I think might be an obscure yet common issue. It was triggered by this question, since then I reviewed my own old code and found some of it was affected by this, too.

在code以下启动和等待两个任务,任务1 和 TASK2 ,这几乎是相同的。 任务1 是唯一不同的TASK2 ,它运行一个永无止境的循环。海事组织,这两种情况下都是一些真实的生活场景进行CPU密集型的工作相当典型的。

The code below starts and awaits two tasks, task1 and task2, which are almost identical. task1 is only different from task2 in that it runs a never-ending loop. IMO, both cases are quite typical for some real-life scenarios performing CPU-bound work.

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 }

为什么状态任务1 是取消,但的状态TASK2 是断陷?注意,在这两种情况下,我做的不的通令牌作为第二个参数为 Task.Run 。

Why the status of task1 is Cancelled, but the status of task2 is Faulted? Note, in both cases I do not pass token as the 2nd parameter to Task.Run.

推荐答案

这里有两个问题。首先,它总是一个好主意,通过的CancellationToken 到 Task.Run API,除了使其可用于任务的拉姆达。这样做有关联的任务令牌,是由 token.ThrowIfCancellationRequested 触发取消。

There are two problems here. First, it's always a good idea to pass CancellationToken to the Task.Run API, besides making it available to the task's lambda. Doing so associates the token with the task and is vital for the correct propagation of the cancellation triggered by token.ThrowIfCancellationRequested.

然而,这并不能解释为什么任务1 仍然得到正确传播( task1.Status == TaskStatus.Canceled ),而它并不适用于 TASK2 ( task2.Status == TaskStatus.Faulted )。

This however doesn't explain why the cancellation status for task1 still gets propagated correctly (task1.Status == TaskStatus.Canceled), while it doesn't for task2 (task2.Status == TaskStatus.Faulted).

现在,这可能是那些非常罕见的情况下,聪明的C#类型推断的逻辑可以针对开发商的将发挥之一。它在很详细这里讨论的这里。与任务1 综上所述,在情况下, Task.Run 是由编译器推断以下重写:

Now, this might be one of those very rare cases where the clever C# type inference logic can play against the developer's will. It's discussed in great details here and here. To sum up, in case with task1, the following override of Task.Run is inferred by compiler:

public static Task Run(Func<Task> function)

而不是:

public static Task Run(Action action)

这是因为任务1 拉姆达拥有了为循环没有天然code的路径,因此它不妨用 Func键&LT;任务&GT; 拉姆达,虽然不能异步键,它不返回任何。这是编译器有利于比动作更多的选项。然后,用这样的覆盖 Task.Run 是相同的:

That's because the task1 lambda has no natural code path out of the for loop, so it may as well be a Func<Task> lambda, despite it is not async and it doesn't return anything. This is the option that compiler favors more than Action. Then, the use of such override of Task.Run is equivalent to this:

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

键入任务℃的嵌套任务;任务&GT; 是 Task.Factory.StartNew 返回,它得到展开以任务按展开()。 Task.Run 的是足够聪明为在受理做这样的自动展开 Func键&LT;任务&GT; 。 的未包装的承诺式任务正确地传播其内部任务,抛出由 Func键℃的 OperationCanceledException 例外取消状态;任务&GT; 拉姆达。这不会发生在 TASK2 ,它接受一个动作 Lambda和不会产生任何内部的任务。取消没有得到传播为 TASK2 ,因为标记尚未与相关TASK2 通过 Task.Run 。

A nested task of type Task<Task> is returned by Task.Factory.StartNew, which gets unwrapped to Task by Unwrap(). Task.Run is smart enough to do such unwrapping automatically for when it accepts Func<Task>. The unwrapped promise-style task correctly propagates the cancellation status from its inner task, thrown as an OperationCanceledException exception by the Func<Task> lambda. This doesn't happen for task2, which accepts an Action lambda and doesn't create any inner tasks. The cancellation doesn't get propagated for task2, because token has not been associated with task2 via Task.Run.

在最后,这可能是任务1 所需的行为(当然不是 TASK2 ),但我们不希望在任一情况下幕后创建嵌套任务。而且,这种行为任务1 可以很容易地得到通过引入一个条件破破出的为循环。

In the end, this may be a desired behavior for task1 (certainly not for task2), but we don't want to create nested tasks behind the scene in either case. Moreover, this behavior for task1 may easily get broken by introducing a conditional break out of the for loop.

正确的code为任务1 应该是

The correct code for task1 should be this:

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

更多推荐

故障VS CancellationToken.ThrowIfCancellationRequested后取消任务状态

本文发布于:2023-11-12 21:39:58,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1582596.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:故障   状态   CancellationToken   ThrowIfCancellationRequested

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!