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

buc*_*ley 4 .net c# task-parallel-library async-await

我正在深入研究这个async-await机制并观察到了一个TaskCanceledException我无法解释的问题.

在下面的示例中(自包含)我有声明

await Task.Run(() => null);
Run Code Online (Sandbox Code Playgroud)

我知道这个声明本身是无用的,但我把问题分开了,真正的代码有逻辑,在某些情况下返回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);
    }
} 
Run Code Online (Sandbox Code Playgroud)

i3a*_*non 10

TaskCanceledException

Task.Run(() => null)返回已取消任务的原因在于重载决策.编译器选择static Task Run(Func<Task> function)而不是static Task<TResult> Run<TResult>(Func<TResult> function)人们所期望的.它就好像你正在召集async代表一样,在这种情况下,你不是.这会导致Task.Run您将返回值(null)展开为一项任务,而这又取消了任务.

负责的特定代码ProcessInnerTask位于UnwrapPromise<TResult>(继承自Task<TResult>)类私有方法中:

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
    }

    // ...
}
Run Code Online (Sandbox Code Playgroud)

您可以通过告诉编译器您没有返回以下内容来轻松告诉编译器不要这样做Task:

var result = await Task.Run(() => (object)null); // Will not throw an exception. result will be null
Run Code Online (Sandbox Code Playgroud)

异常处理

这两种方法之间的区别在于,TestAsyncExceptionOnlyInTheOutputWindow您不会await出现故障任务,因此永远不会重新存储任务中存储的异常.

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

例外