为什么TaskCanceledException抛出并不总是中断到调试并不、抛出、TaskCanceledException

2023-09-04 01:05:21 作者:那将

我挖成异步计谋机制和观察的投掷 TaskCanceledException ,我可以'牛逼解释呢。

在下面的例子中(自包含的)我有说法

 等待Task.Run(()=>空);
 

我知道,对自己这种说法是没有用的,但我孤立的问题,真正的code的逻辑,在某些情况下返回null。

为什么这个抛出一个 TaskCanceledException ?如果我返回(在下面的示例中5)的任意数也不会抛出。

此外,如果我等待的方法VS中断的调试,但如果我不去计谋它那么只有消息被写入VS的输出窗口

 内部类节目
{
    私有静态无效的主要(字串[] args)
    {
        VAR testAsync =新TestAsync();

        //抛出异常,但调试器不介入,只有一个消息记录到输出窗口
        testAsync.TestAsyncExceptionOnlyInTheOutputWindow();

        //抛出异常和调试器中断
        testAsync.TestAsyncExceptionBreaksIntoTheDebugger();

        Console.ReadKey();
    }
}

内部类TestAsync
{
    公共异步无效TestAsyncExceptionOnlyInTheOutputWindow()
    {
         TestNullCase();
    }

    公共异步无效TestAsyncExceptionBreaksIntoTheDebugger()
    {
        等待TestNullCase();
    }

    私有静态异步任务TestNullCase()
    {
        //这不会引发TaskCanceledException
        等待Task.Run(()=大于5);

        //这不会抛出一个TaskCanceledException
        等待Task.Run(()=>空);
    }
}
 
积分榜第一期已经新鲜出炉

解决方案

TaskCanceledException

原因 Task.Run(()=>空)返回已取消的任务是掌握在重载。编译器选择静态任务运行(Func键<任务>功能),而不是静态任务< TResult>运行< TResult>(Func键< TResult>功能)为一个可以预期。它充当如果你调用一个异步委托,在这种情况下,你不是。这导致了 Task.Run 展开您的返回值(空)的这反过来会取消任务任务。

具体code责任是在 ProcessInnerTask 在私有方法的 UnwrapPromise< TResult> (从任务< TResult> )类:

 私人无效ProcessInnerTask(任务的任务)
{
    //如果内任务为空,代理将被取消。
    如果(任务== NULL)
    {
        TrySetCanceled(默认值(的CancellationToken));
        _state = STATE_DONE; // ...和记录,我们正在做
    }

    // ...
}
 

您可以轻松地告诉编译器不能做到这一点告诉编译器不必返回一个工作

  VAR的结果=等待Task.Run(()=>(对象)空); //不会抛出异常。结果将是空
 

异常处理

这两种方法之间的区别在于,在 TestAsyncExceptionOnlyInTheOutputWindow 你不这样做等待的故障的任务,因此在异常存储在任务被从未重新引发

您可以通过检查使这两种方法调试器突破的抛出的上的公共语言运行库异常的在你的设置(调试=>例外)列:

I'm digging into the async-await mechanism and observed the throwing of a TaskCanceledException that I can't explain yet.

In the sample below (self contained) I have the statement

await Task.Run(() => null);

I know that this statement on itself is useless but I isolated the issue, the real code has logic and returns null in some cases.

Why does this throw a TaskCanceledException? If I return an arbitrary number (5 in the below example) it does not throw.

Furthermore if I await the method the debugger of VS breaks but If I don't await it then only a message is written to the output window of 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

The reason Task.Run(() => null) returns a canceled task rests in overload resolution. The compiler chooses static Task Run(Func<Task> function) and not static Task<TResult> Run<TResult>(Func<TResult> function) as one may expect. It acts as if you're calling an async delegate, which in this case you're not. That results in Task.Run "unwrapping" your return value (null) as a task which in turn would cancel the task.

The specific code responsible for that is in the 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
    }

    // ...
}

You can easily tell the compiler not to do that by telling the compiler you are not returning a Task:

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

Exception Handling

The difference between the two methods is that in TestAsyncExceptionOnlyInTheOutputWindow you don't await the faulted task and so the exception stored in the task is never rethrown.

You can make the debugger break in both methods by checking the thrown column on Common Language Runtime Exceptions in your settings (Debug => Exceptions):