-1

Where an event handler calls async code:

MyObject.MyEvent += MyHandler;

Is there any advantage of using one of these patterns over the other?:

// option 1: async void + await
private async void MyHandler(object sender, EventArgs args)
{
  await DoSomethingAsync();
  DoSomethingElse();
}

// option 2: void + discard syntax
private void MyHandler(object sender, EventArgs args)
{
  _ = DoSomethingAsync();
  DoSomethingElse();
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Ross
  • 4,460
  • 2
  • 32
  • 59

2 Answers2

2

In case of option 1 DoSomethingElse() will execute only after DoSomethingAsync() finished executing.

In case of option 2 DoSomethingElse() will not wait for DoSomethingAsync() to finish.

Gabor
  • 3,021
  • 1
  • 11
  • 20
1
private async void MyHandler(object sender, EventArgs args)
{
  await DoSomethingAsync();
  DoSomethingElse();
}

The execution path of this function completely executes DoSomethingAsync(), then runs DoSomethingElse(). The exact thread that both of these operations run on is undefined (meaning it can run anywhere). Additionally, any errors in DoSomethingAsync will prevent DoSomethingElse() from running, and will call an event handler on your SynchronizationContext sometime down the line alerting you of the failure.

private void MyHandler(object sender, EventArgs args)
{
  _ = DoSomethingAsync();
  DoSomethingElse();
}

The execution path of this function runs DoSomethingAsync() until the first await expression, where it schedules the execution of the rest of DoSomethingAsync(). It will then run DoSomethingElse() synchronously.

This means that everything after the first await of DoSomethingAsync can run anywhere, at any time, and is not necessarily going to run before DoSomethingElse(). You will never know if any awaits in DoSomethingAsync run at all, or whether it runs successfully, as any errors in DoSomethingAsync() will not prevent DoSomethingElse() from running (including those in the synchronous block!). In practice, if this is all you need from this pattern, then you should just use try/catch.

To clarify, The first synchronous block of DoSomethingAsync() will run until the first await. After that, you are in the blind. Note that DoSomethingAsync failing in your void pattern will behave exactly the same as MyHandler failing in your async void pattern.


Your first pattern (async void) strongly defines the control-flow (do A, then do B), while your second (void) pattern loosely defines the control flow (do A until the first await, do B, and sometime in the future finish doing A.).

Note that both patterns are exactly the same to whatever code is calling your event handler; each just has different benefits and consequences for your end of the stick.

You can read more about return types and exception behavior with these patterns here:

Mooshua
  • 526
  • 2
  • 16
  • 2
    _"The execution path of this function schedules the execution of DoSomethingAsync(), then runs DoSomethingElse()" "You will never know if DoSomethingAsync runs at all,[...]"_ - I feel like this part of your answer requires clarification. The code won't just "schedule the execution"of DoSomethingAsync, it will actually enter the function immediately, and will run synchronously until it encounters the first `await` inside it, at which point it will exit (return to `MyHandler`) and enter `DoSomethingElse`. – Orion Sep 01 '23 at 22:15
  • @Orion You are correct, and this is defined behavior in the C# specification, but I just wasn't sure of a way to clearly word it without confusing people. I'll try and work something in. – Mooshua Sep 01 '23 at 22:56
  • "The exact thread that both of these operations run on is undefined" is a bit misleading. This seems to be an event handler in a GUI application, so there is a synchronization context which will make `DoSomethingElse` continue on the UI thread. – Klaus Gütter Sep 02 '23 at 04:59
  • Ah, that’s a semantics error on my part. Undefined here meaning not defined in any standard I could find, not that the thread itself is completely unpredictable 100% of the time or actually invokes undefined behavior. – Mooshua Sep 02 '23 at 08:27