1

I find myself using async fire-and-forget methods using void as the return value, but DO care about exceptions.

It seems to be the consensus that exceptions cannot be handled properly with async-await if no reference is hold to the executing Task and void should be.. well.. avoided..

What am I missing in the following code that apparently seems to do the job:

class Program
{
    static void Main()
    {
        var p = new Processor();
        p.ExceptionThrown += p_ExceptionThrown;
        for (var i = 0; i < 10; i++)
            p.ProcessAsync(i);
        Console.ReadKey();
    }

    static void p_ExceptionThrown(object sender, Exception e)
    {
        Console.WriteLine("Exception caught in Main : " + e);
    }
}

class Processor
{
    public async void ProcessAsync(int iteration)
    {
        try
        {
            await Task.Run(() => Process(iteration));
        }
        catch (Exception e)
        {
            ExceptionThrown?.Invoke(this, e);
        }
    }

    static void Process(int iteration)
    {
        Thread.Sleep(500);
        if(iteration == 5)
            throw new Exception("AUUCH");
    }

    public event EventHandler<Exception> ExceptionThrown;
}
noontz
  • 1,782
  • 20
  • 29
  • 5
    Nothing. If the method doesn't throw any exceptions (and it doesn't if you catch them all inside the method) then the return value doesn't matter regarding exceptions. – i3arnon Jan 12 '16 at 09:05
  • Thanks. I´m relieved ;).. If you post that I´ll mark it as the answer – noontz Jan 12 '16 at 09:11
  • 2
    @noontz You are missing something, you're missing the usage of the `Task` class. – David Pine Jan 12 '16 at 15:49
  • What would you like to happen in case the `p_ExceptionThrown` handler throws an exception? Is it desirable for the process to crash in this case? – Theodor Zoulias Jun 08 '23 at 21:45

1 Answers1

-1

Under the covers the async / await keywords actually generate a state-machine when they are compiled down to IL, please read about it here. The only time that you should ever use async void is on an event handler, as explained here. The issue is that when the state machine is built-out it uses the Task or Task<T> classes as the return type in order to manage the next state of the next asynchronous operation in the chain. However, when you define the method as void, it basically returns null to the state machine and then everything gets out of whack.

Exceptions from an async void can’t be caught with catch

The quote from above is from the best practices article I pointed you to before. The below alteration does work, as I have tested it to verify that it does.

class Program
{
    static void Main()
    {
        var p = new Processor();
        p.ExceptionThrown += p_ExceptionThrown;
        for (var i = 0; i < 10; i++)
            p.ProcessAsync(i);
        Console.ReadKey();
    }

    static void p_ExceptionThrown(object sender, Exception e)
    {
        Console.WriteLine("Exception caught in Main : " + e);
    }
}

class Processor
{
    public async Task ProcessAsync(int iteration)
    {
        try
        {
            await Task.Run(() => Process(iteration));
        }
        catch (Exception e)
        {
            OnException(e);
        }
    }

    public void Process(int iteration)
    {
        Thread.Sleep(500);
        if(iteration == 5)
            throw new Exception("AUUCH");
    }

    public event EventHandler<Exception> ExceptionThrown;

    void OnException(Exception e)
    {
        var handler = ExceptionThrown;
        if (handler != null)
            handler(this, e);
    }
}
David Pine
  • 23,787
  • 10
  • 79
  • 107
  • Sure your code will run, but now you have the compiler error from the linked post that triggered the question. If you try and run the original code you will see that "Exceptions from an async void can´t be caught with catch" is from a different context. An eventhandler is just a method: What prevents "everything gets out of whack"? – noontz Jan 13 '16 at 06:47
  • @noontz, I guess perhaps I'm confused what you're asking. Could you please elaborate a little, what do you want to know? I re-read your question several times and the link too... – David Pine Jan 13 '16 at 13:53
  • I´m asking if what I´m doing will catch exceptions properly. I'm not bothered with the result or state of the Task. Sorry if my question is somewhat ambiguous. – noontz Jan 13 '16 at 14:19
  • The answer is no, as I have answered... You need to have Task be the return type, it is that simple. – David Pine Jan 13 '16 at 14:20
  • 1
    hmmm.. The comment from i3arnon suggests it´s not that simple. - – noontz Jan 13 '16 at 14:31
  • 1
    Right. As i3arnon pointed out in first comment on question, the `async void` return is moot, because `ProcessAsync` catches all exceptions inside of it. Unless I've missed something, that means this answer is not needed. Bottom line: best practice for `async void` methods, is to explicitly catch all exceptions using `try .. catch ..`. For example, in Xamarin.Forms and Maui, you can't change the signature of event handlers. There is no useful place for a `Task` to be returned to; instead you should handle exceptions yourself. – ToolmakerSteve Feb 07 '23 at 04:59