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

时间:2014-10-26 20:23:56

标签: c# .net task-parallel-library async-await

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

3 个答案:

答案 0 :(得分:9)

TaskCanceledException

Task.Run(() => null)返回已取消任务的原因在于重载解析。编译器可以选择static Task Run(Func<Task> function)而不是static Task<TResult> Run<TResult>(Func<TResult> function)。它的行为就好像您正在呼叫async代表,在这种情况下,您不会。这导致Task.Run&#34;展开&#34;您的返回值(null)作为一项任务,反过来会取消任务。

负责此操作的具体代码位于ProcessInnerTask private method in the UnwrapPromise<TResult> (inherits from Task<TResult>) class

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 =&gt; Exceptions)来使这两种方法中的调试器中断:

Exceptions

答案 1 :(得分:3)

当你调用Task.Run(()=&gt; 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(()=&gt; 5);它不会抛出异常。

但实际上是什么

public static Task<TResult> Run<TResult>(Func<Task<TResult>> function) 

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

 public static Task<TResult> Run<TResult>(Func<Task<TResult>> function) 
     语言编译器使用

方法来支持异步和等待   关键字。它不能直接从用户代码

调用

MSDN

答案 2 :(得分:0)

只是一个观察,可能会引导你找出真正的答案......如果用方法替换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;
    }

<强>更新

让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>,则无需任何其他更改即可运行。