Task cancellation exception not bubbling up when u

2019-07-10 13:40发布

问题:

I don't really understand this. In my code below, run as-is, the exception thrown by token.ThrowIfCancellationRequested() is not being sent back to where Main is using .Wait(), and is instead thrown on the spot with the stacktrace not really knowing where it happened other than "A task was cancelled". However if I remove the async keyword and remove the try catch block with the await Task.Delay(), it does get sent back to .Wait() in main and is caught there.

Am I doing something wrong, or how exactly do I get both the exception thrown by await Task.Delay() and token.ThrowIfCancellationRequested() to both bubble up to .Wait()?

static void Main(string[] args)
{
    var t = new CancellationTokenSource();
    var task = Task.Factory.StartNew((x) => Monitor(t.Token), t.Token, TaskCreationOptions.LongRunning);

    while (true)
    {
        if (Console.ReadLine() == "cancel")
        {
            t.Cancel();
            try 
            {
                task.Wait();
            }
            catch(AggregateException)
            {
                Console.WriteLine("Exception via task.Wait()");

            }
            Console.WriteLine("Cancelled");
        }
    }
}

static async void Monitor(CancellationToken token)
{
    while(true)
    {
        for(int i = 0; i < 5; i++)
        {
            // If the async in the method signature, this does not send the exception to .Wait();
            token.ThrowIfCancellationRequested();

            // Do Work
            Thread.Sleep(2000);
        }

        // Wait 10 seconds before doing work again.

        // When this try block is removed, and the async is taken out of the method signature,
        // token.ThrowIfCancellationRequested() properly sends the exception to .Wait()
        try
        {
            await Task.Delay(10000, token);
        } 
        catch(TaskCanceledException) 
        {
            Console.WriteLine("Exception from Delay()");
            return;
        }
    }
}

回答1:

You should avoid async void. Its exception handling semantics are tricky, and it's not possible to compose into other async methods.

async void methods are conceptually event handlers, so if it throws an exception, it will be raised directly on its SynchronizationContext - in this case, thrown on a thread pool thread.

The asynchronous equivalent of a void-returning method is not an async void-returning method; it is an async Task-returning method. So your Monitor method should return Task:

static void Main(string[] args)
{
  var t = new CancellationTokenSource();
  var task = Monitor(t.Token);

  while (true)
  {
    if (Console.ReadLine() == "cancel")
    {
      t.Cancel();
      try 
      {
        task.Wait();
      }
      catch(AggregateException)
      {
        Console.WriteLine("Exception via task.Wait()");
      }
      Console.WriteLine("Cancelled");
    }
  }
}

static async Task Monitor(CancellationToken token)

Don't worry about missing the LongRunning flag; it's just an optimization and the thread pool will work fine without it.

You may find my async/await intro or the official MSDN documentation helpful.