Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
450 views
in Technique[技术] by (71.8m points)

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

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);
    }
} 
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...