Why is TaskCanceledException thrown and does not always breaks into the debugger

后端 未结 3 1817
孤独总比滥情好
孤独总比滥情好 2020-12-11 07:17

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

In the sample b

相关标签:
3条回答
  • 2020-12-11 07:52

    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):

    Exceptions

    0 讨论(0)
  • 2020-12-11 07:57

    It seems when you call Task.Run (()=> null) it will choose

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

    overload of function and when you return null the result task proxy is somehow faulty, if you use

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

    Instead it will pick the right overload

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

    like your int sample Task.Run(() => 5); and it wont throw exception.

    But what actually

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

    overload mean I could not find the answer.

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

    method is used by language compilers to support the async and await keywords. It is not intended to be called directly from user code

    .

    MSDN

    0 讨论(0)
  • 2020-12-11 08:11

    If you replace the Func<T> with a method, it passes.

        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

    After letting ReSharper convert both lambdas to variables:

        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);
        }
    

    So, the second form is incorrectly interpreted as Func<Task> instead of your intent, which I believe is Func<object>. And because the Task passed in is null, and because you can't execute a null, you get a TaskCanceledException. If you change the variable type to Func<object> it works without any additional changes.

    0 讨论(0)
提交回复
热议问题