[英]Why is TaskCanceledException thrown and does not always breaks into the debugger
我正在深入研究async-await
机制并观察到抛出一个我无法解释的TaskCanceledException
。
在下面的示例中(自包含)我有声明
await Task.Run(() => null);
我知道这个声明本身是无用的,但我把问题分开了,真正的代码有逻辑,在某些情况下返回null。
为什么抛出TaskCanceledException
? 如果我返回一个任意数字(在下面的例子中为5),它就不会抛出。
此外,如果我await
该方法VS的调试器中断,但如果我不await
它,那么只有一条消息被写入VS的输出窗口。
internal class Program
{
private static void Main(string[] args)
{
var testAsync = new TestAsync();
// Exception thrown but the debugger does not step in. Only a message is logged to the output window
testAsync.TestAsyncExceptionOnlyInTheOutputWindow();
// Exception thrown and the debugger breaks
testAsync.TestAsyncExceptionBreaksIntoTheDebugger();
Console.ReadKey();
}
}
internal class TestAsync
{
public async void TestAsyncExceptionOnlyInTheOutputWindow()
{
TestNullCase();
}
public async void TestAsyncExceptionBreaksIntoTheDebugger()
{
await TestNullCase();
}
private static async Task TestNullCase()
{
// This does not throw a TaskCanceledException
await Task.Run(() => 5);
// This does throw a TaskCanceledException
await Task.Run(() => null);
}
}
Task.Run(() => null)
返回已取消任务的原因在于重载 Task.Run(() => null)
。 编译器选择static Task Run(Func<Task> function)
而不是static Task<TResult> Run<TResult>(Func<TResult> function)
如人们所期望的那样。 它就像你正在调用async
委托一样,在这种情况下你不是。 这导致Task.Run
将您的返回值( null
)“展开”为任务,而该任务又会取消该任务。
负责该操作的特定代码位于UnwrapPromise<TResult>
(继承自Task<TResult>
)类的ProcessInnerTask
私有方法中 :
private void ProcessInnerTask(Task task)
{
// If the inner task is null, the proxy should be canceled.
if (task == null)
{
TrySetCanceled(default(CancellationToken));
_state = STATE_DONE; // ... and record that we are done
}
// ...
}
您可以通过告诉编译器您没有返回Task
来轻松告诉编译器不要这样做:
var result = await Task.Run(() => (object)null); // Will not throw an exception. result will be null
两种方法之间的区别在于,在TestAsyncExceptionOnlyInTheOutputWindow
您不await
出现故障的任务,因此永远不会重新抛出存储在任务中的异常。
您可以通过检查设置中公共语言运行时异常的抛出列(Debug => Exceptions)来使这两种方法中的调试器中断:
看来,当你调用Task.Run(()=> null)时,它会选择
public static Task<TResult> Run<TResult>(Func<Task<TResult>> function)
函数重载,当你返回null时,结果任务代理在某种程度上是错误的,如果你使用
Task.Run (()=> (object)null)
相反,它会选择正确的过载
Task<TResult> Run<TResult>(Func<TResult> function)
喜欢你的int示例Task.Run(()=> 5); 它不会抛出异常。
但实际上是什么
public static Task<TResult> Run<TResult>(Func<Task<TResult>> function)
超载意味着我找不到答案。
public static Task<TResult> Run<TResult>(Func<Task<TResult>> function)
语言编译器使用方法来支持async和await关键字。 它无意直接从用户代码调用
。
只是一个观察,可能会引导你找出真正的答案......如果用方法替换Func<T>
,它就会通过。
private static async Task TestNullCase()
{
// This does not throw a TaskCanceledException
await Task.Run(() => 5);
// This does throw a TaskCanceledException
await Task.Run(() => GetNull());
}
private static object GetNull()
{
return null;
}
UPDATE
让ReSharper将两个lambda都转换为变量之后:
private static async Task TestNullCase()
{
// This does not throw a TaskCanceledException
Func<int> func = () => 5;
await Task.Run(func);
// This does throw a TaskCanceledException
Func<Task> function = () => null;
await Task.Run(function);
}
所以,第二种形式被错误地解释为Func<Task>
而不是你的意图,我相信它是Func<object>
。 并且因为传入的Task为null,并且您无法执行null,所以会得到TaskCancellledException。 如果将变量类型更改为Func<object>
则无需任何其他更改即可运行。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.