任务行为不同于 Task<T>抛出 OperationCanceledException 时的行为

编程入门 行业动态 更新时间:2024-10-16 08:31:58
本文介绍了任务行为不同于 Task<T>抛出 OperationCanceledException 时的行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

限时送ChatGPT账号..

为什么这个测试失败了?
据我所知,t1t2 之间的唯一区别是 t1 是一个 Task,而 t2 是一个 Task.然而出于某种原因,t2 最终处于 Faulted 状态,而不是 Canceled 状态.为什么行为会不同?

Why does this test fail?
The only difference between t1 and t2, as far as I can tell, is that t1 is a Task, and t2 is a Task<int>. Yet for some reason t2 ends up in the Faulted status, as opposed to the Canceled status. Why would the behavior differ?

[Test]
public void Test_foo()
{
    var t1 = Task.Run(() =>
    {
        throw new OperationCanceledException();
    });
    try
    {
        t1.Wait();
    }
    catch (AggregateException e)
    {
        Assert.IsTrue(t1.IsCanceled);
    }

    var t2 = Task.Run(() =>
    {
        throw new OperationCanceledException();
        return 1;
    });
    try
    {
        t2.Wait();
    }
    catch (AggregateException e)
    {
        Assert.IsTrue(t2.IsCanceled); // fails, it's Faulted 
    }
}

推荐答案

您的任务之间的主要区别在于您使用的 Task.Run 方法的重载:

The main difference between your tasks is the overload for the Task.Run method you're using:

task1 是使用 Task 创建的.Run 方法 (Func),而不是 task2 是用 Task.Run方法(Func).这个重载确实创建了一个有点不同的任务:

task1 is created with Task.Run Method (Func<Task>), rather than task2 is created with Task.Run<TResult> Method (Func<TResult>). This overloads do create task with a little bit difference:

对于 task1结果 属性设置为 System.Threading.Tasks.VoidTaskResultCreationOptions 设置为 None,而不是 task2 CreationOptions 设置为DenyChildAttach,结果是一个default(int),也就是0. for task1 the Result property is set to System.Threading.Tasks.VoidTaskResult, and CreationOptions is set to None, rather than task2 the CreationOptions is set to DenyChildAttach, and the result is a default(int), which is 0.

当您等待 task2 时,Result 属性未设置为实际值,因为抛出了异常.根据 MSDN:

当任务实例观察到 OperationCanceledException 抛出用户代码,它将异常的令牌与其关联的令牌进行比较(传递给创建 Task 的 API 的那个).如果他们是相同的和令牌的 IsCancellationRequested 属性返回true,任务将此解释为确认取消并转换到 Canceled 状态.如果您不使用 等待 或 <代码>WaitAll方法等待任务,然后任务只是将其状态设置为取消.

When a task instance observes an OperationCanceledException thrown by user code, it compares the exception's token to its associated token (the one that was passed to the API that created the Task). If they are the same and the token's IsCancellationRequested property returns true, the task interprets this as acknowledging cancellation and transitions to the Canceled state. If you do not use a Wait or WaitAll method to wait for the task, then the task just sets its status to Canceled.

如果您正在等待转换为Canceled 状态,<代码>System.Threading.Tasks.TaskCanceledException异常(包装在 AggregateException 异常)被抛出.笔记此异常表示成功取消而不是错误的情况.因此,任务的Exception 属性返回.

If you are waiting on a Task that transitions to the Canceled state, a System.Threading.Tasks.TaskCanceledException exception (wrapped in an AggregateException exception) is thrown. Note that this exception indicates successful cancellation instead of a faulty situation. Therefore, the task's Exception property returns null.

如果令牌的 IsCancellationRequested 属性返回 false如果异常的令牌与任务的令牌不匹配,则OperationCanceledException 是视为正常异常,导致转换到 Faulted 状态的任务.还要注意的是其他异常的存在也会导致 Task 转换为Faulted 状态.您可以在状态 财产.

If the token's IsCancellationRequested property returns false or if the exception's token does not match the Task's token, the OperationCanceledException is treated like a normal exception, causing the Task to transition to the Faulted state. Also note that the presence of other exceptions will also cause the Task to transition to the Faulted state. You can get the status of the completed task in the Status property.

所以,在这里我们可以找到这种行为的原因 - 由于令牌不匹配,异常被视为正常异常.这很奇怪,因为令牌是绝对相同(我已经在调试中检查过,哈希码是相等的,Equals 方法和双等号运算符返回 true),但比较仍然返回 false.因此,针对您的情况的解决方案是显式使用 取消令牌,类似这样(我添加了 Thread.Sleep 以避免竞争条件):

So, here we can find the reason for this behavior - the exception is treated like a normal exception because of the token mismatch. This is strange, because the token is definitely the same (I've checked that in Debug, the hash code is equal, Equals method and double equals operator returns true), but the comparison still returns false. So, the solution for your case is explicit usage of the cancellation tokens, something like this (I've added the Thread.Sleep to avoid the race condition):

var t1TokenSource = new CancellationTokenSource();
var t1 = Task.Run(() =>
{
    Thread.Sleep(1000);
    if (t1TokenSource.Token.IsCancellationRequested)
    {
        t1TokenSource.Token.ThrowIfCancellationRequested();
    }
    //throw new TaskCanceledException();
}, t1TokenSource.Token);
try
{
    t1TokenSource.Cancel();
    t1.Wait();
}
catch (AggregateException e)
{
    Debug.Assert(t1.IsCanceled);
}

var t2TokenSource = new CancellationTokenSource();
var t2 = Task.Run(() =>
{
    Thread.Sleep(1000);
    if (t2TokenSource.Token.IsCancellationRequested)
    {
        t2TokenSource.Token.ThrowIfCancellationRequested();
    }
    //throw new TaskCanceledException();
    return 1;
}, t2TokenSource.Token);
try
{
    t2TokenSource.Cancel();
    t2.Wait();
}
catch (AggregateException e)
{
    Debug.Assert(t2.IsCanceled);
}

来自 MSDN 的另一句话:

您可以使用以下选项之一终止操作:

You can terminate the operation by using one of these options:

通过简单地从委托返回.在许多情况下,这已经足够了;但是,以这种方式取消的任务实例转换到 TaskStatus.RanToCompletion 状态,而不是TaskStatus.Canceled 状态.通过抛出 OperationCanceledException 并将请求取消的令牌传递给它.执行此操作的首选方法是使用 ThrowIfCancellationRequested 方法. 一个任务是以这种方式取消转换到 Canceled 状态,调用代码可以用来验证任务是否响应了它的取消请求. By simply returning from the delegate. In many scenarios this is sufficient; however, a task instance that is canceled in this way transitions to the TaskStatus.RanToCompletion state, not to the TaskStatus.Canceled state. By throwing a OperationCanceledException and passing it the token on which cancellation was requested. The preferred way to do this is to use the ThrowIfCancellationRequested method. A task that is canceled in this way transitions to the Canceled state, which the calling code can use to verify that the task responded to its cancellation request.

如您所见,首选方式可预测地工作,而直接异常抛出则不然.另请注意,如果使用 task 也是使用 DenyChildAttach 创建的,并且没有 Result 属性,因此您遇到的构造函数存在一些差异.
希望这会有所帮助.

As you can see, the preffered way is working predictably, the direct exception throw does not. Please also note that in case of usage task is created with DenyChildAttach too, and doesn't have a Result property, so there is some difference in constructors, which you've faced with.
Hope this helps.

这篇关于任务行为不同于 Task&lt;T&gt;抛出 OperationCanceledException 时的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

更多推荐

[db:关键词]

本文发布于:2023-05-01 09:57:07,感谢您对本站的认可!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:不同于   抛出   Task   lt   amp

发布评论

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

>www.elefans.com

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