问题描述
限时送ChatGPT账号..为什么这个测试失败了?
据我所知,t1
和 t2
之间的唯一区别是 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.VoidTaskResult
和 CreationOptions
设置为 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 theTask
). If they are the same and the token'sIsCancellationRequested
property returns true, the task interprets this as acknowledging cancellation and transitions to theCanceled
state. If you do not use aWait
orWaitAll
method to wait for the task, then the task just sets its status toCanceled
.
如果您正在等待转换为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<T>抛出 OperationCanceledException 时的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
更多推荐
[db:关键词]
发布评论