为什么抛出TaskCanceledException并且不会总是进入调试器

我正在深入研究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); } } 

TaskCanceledException

Task.Run(() => null)返回已取消任务的原因在于重载 Task.Run(() => null) 。 编译器选择static Task Run(Func function)而不是static Task Run(Func function) 。 它就像你正在调用async委托一样,在这种情况下你不是。 这导致Task.Run将您的返回值( null )“展开”为任务,而该任务又会取消该任务。

负责该操作的特定代码位于UnwrapPromise (inheritance自Task )类的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 

exception处理

这两种方法的区别在于,在TestAsyncExceptionOnlyInTheOutputWindow您不await出现故障的任务,因此永远不会重新抛出存储在任务中的exception。

您可以通过检查设置中公共语言运行时exception抛出列(Debug => Exceptions)来使这两种方法中的调试器中断:

例外

看来,当你调用Task.Run(()=> null)时,它会选择

  public static Task Run(Func> function) 

函数重载,当你返回null时,结果任务代理在某种程度上是错误的,如果你使用

 Task.Run (()=> (object)null) 

相反,它会选择正确的过载

 Task Run(Func function) 

喜欢你的int示例Task.Run(()=> 5); 它不会抛出exception。

但实际上是什么

 public static Task Run(Func> function) 

超载意味着我找不到答案。

  public static Task Run(Func> function) 

语言编译器使用方法来支持async和await关键字。 它无意直接从用户代码调用

MSDN

只是一个观察,这可能会导致你找到真正的答案……如果用方法替换Func ,它就会通过。

  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 func = () => 5; await Task.Run(func); // This does throw a TaskCanceledException Func function = () => null; await Task.Run(function); } 

所以,第二种forms被错误地解释为Func而不是你的意图,我相信它是Func 。 并且因为传入的Task为null,并且您无法执行null,所以会得到TaskCancellledException。 如果将变量类型更改为Func则无需任何其他更改即可运行。