none
handled unhandled exception with async/await? RRS feed

  • Question

  • I wrote the following test in XUnit (based on simplified real use case)..

    It passes just fine, but if I debug, it says user unhandled exception in _Throw, but if I put a breakpoint on the catch in ensureHandled and continue, it hits the catch.  Is this a bug in the debugger or..?

    I'm wondering if the debugger is looking at the callstack only for handlers?

    I'm on visual studio 2017 enterprise v15.6.4

    public class AsyncTests {
    
      [Fact]
      public async Task Test() {
        var handled = false;
    
        async Task ensureHandled(Task task) {
          try {
            await task;
          } catch (Exception e) {
            handled = true;
          }
        }
    
        try {
          var task = Task.Run(this._Throw);
          await Task.WhenAny(new List<Task> {
            ensureHandled(task),
            this._Delay0()
          });
        } catch (Exception) {
          handled = true;
        }
    
        await Task.Delay(5000);
    
        Assert.True(handled);
      }
    
    
      private async Task _Delay0() {
        await Task.Yield();
      }
    
      private Task _Throw() {
        Thread.Sleep(50);
        throw new Exception();
      }
    } 






    Friday, April 20, 2018 6:40 AM

All replies

  • I cannot replicate your issue with MSTest so I'm going to wager it is an issue with XUnit. You'll need to post in their forums. The code, while peculiar, is valid from what I can tell. The delays and whatnot seem arbitrary but don't really hurt anything.

    Michael Taylor http://www.michaeltaylorp3.net

    Friday, April 20, 2018 2:14 PM
    Moderator
  • Here is console app version that exhibits the issue for me:

    I targeted net461, and language version latest

    using System;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;
     
    namespace ConsoleApp1 {
      public class Program {
        public static async Task Main(string[] args) {
          async Task ensureHandled(Task task) {
            try {
              await task;
            } catch (Exception e) {
              Console.WriteLine(e);
            }
          }
     
          try {
            var task = Task.Run(_Throw);
            await Task.WhenAny(new List<Task> {
              ensureHandled(task),
              _Delay0()
            });
          } catch (Exception e) {
            Console.WriteLine(e);
          }
     
          await Task.Delay(5000);
        }
    
        private static async Task _Delay0() =>
          await Task.Yield();
     
        private static Task _Throw() {
          Thread.Sleep(50);
          throw new Exception();
        }
      }
    }

    The delays were simply to ensure that the first catch from the top got hit, as opposed to the second one, as the first one is the one that exhibits the issue of not being detected by the debugger as "user handling".. I'm guessing it's related to that catch not being in the call stack of the call to _Throw.


    Friday, April 20, 2018 10:34 PM
  • Working fine for me. I'm using 15.7 preview 4.  Main starts a task to call _Throw and then calls ensureHandled. That method is called on the same thread and then awaits on the task running _Throw. Note that at this point that task may have already completed. Irrelevant the await keyword causes the main thread to stall until the task completes. It is at this point that _Delay0 may be called (on the main thread). It simply yields the task which would return the task back to the main thread (inside WhenAny) which will then block (because of the await) until one of the tasks complete.

    When the _Throw task completes we're back on the main thread again. If the task threw an exception then the exception is raised on the main thread (not really relevant here). The current stack (on the main thread) has a catch block that handles the exception so it is eaten and the task returns normally. If this is the first task to complete in your WhenAny then the main function continues otherwise nobody is listening for that task to complete so it is effectively ignored.

    Note that the catch inside the main function itself may or may not handle exceptions from either of the tasks being awaited in WhenAny. When either task is completed then the code continues (back on the main thread). If the other task throws an unhandled exception at this point then it won't be seen by your app because you're no longer awaiting it. This may be the scenario you're seeing if you are putting your breakpoint on, say, the await in ensureHandled. Then the other task will complete first and the main thread will continue on. 

    If you believe you have identified a bug in the debugger then you should use the Report a Problem in Visual Studio to submit your scenario and code and the debugger team can look at it. I believe the code is behaving correctly, in the sense that you have 1 task that is never going to be monitored for results. Depending upon where the breakpoints are set it is possible for the task to complete after the main thread has finished. It may also be related to you using the main thread as the sync context. I wonder if you'd see different results if you used ConfigureAwait(false) on your code. This is the recommended approach in non-UI code anyway to help avoid deadlocks on the main thread.


    Michael Taylor http://www.michaeltaylorp3.net

    Saturday, April 21, 2018 2:32 AM
    Moderator
  • Yeah I'm aware that I'm not awaiting the result of the _Throw task and that there are some bad practice issues with tasks here.  Im just confused on why the debugger is reporting that the exception is unhandled, when I clearly have that task awaited within a try/catch block, and the catch is in fact hit with this exception lol.

    Wednesday, April 25, 2018 8:06 AM