BrandGhost

What you DIDN'T know about C# Async EventHandlers

async and EventHandlers in C# just don't seem to mix very well. As soon as you have exceptions, you're in for a world of pain and often left wondering just what the heck broke your application. If you've been having headaches with async event handlers, then check this out! Disclaimer: While finalizing the blog post for the is article (which came after the video was recorded) I discovered a pretty big flaw with this approach. When you have multiple subscriptions to the same Task EventHandler, wh...
View Transcript
Event handlers and the async await pattern are something that just don't seem to mix well in C. So before I get into this, just curious to hear from you in the comments if you've come up with any patterns yourself that you've been using to mix these two things together effectively. Okay, so previous to this video, I've written and created content around how you can actually work around async void, which is one of the big problems we end up seeing when we have event handlers in C that we want to use the async await pattern for. I had feedback that some people didn't like the approach I took because they thought there could be cleaner ways that you could wire up the async await pattern to event handlers. And I think those people are right. I think we do have some other solutions more specifically for event handlers. So I wanted to jump into some of those examples today. So if that sounds interesting, if you could do me a favor, give the video a thumbs up, subscribe to the channel, and with that said, let's jump over to Visual Studio. So I'm here in Visual Studio with our first example that we're going to look at. And the purpose of this example is to demonstrate async void on event handlers. And this is sort of the pattern that we're told that it's the only time it's okay to use async void, otherwise it's pretty scary. When we go to run this example, we're going to notice that our event handler that's marked as async void. When it eventually throws an exception, it's actually something that our application can't easily catch. So, I'm going to go ahead and walk us through the code, then run it, and we'll talk about what's happening. I've kind of just marked in console writing here that we're going to be starting the example. And you can see at the end we'll say that it's actually complete. These are just going to be markers in our console that we can refer back to. But what I'm doing here is that I have an object that's going to be very simple. When we look at it, we'll see that it just has a normal event handler. I've left a comment here saying like it doesn't have to just be these event args. You could use whatever. And then the other method that it has is actually the ability for us to invoke this event from the outside of the class. So usually you don't have this kind of thing like the class itself would know how to raise its events properly but this is just going to be so that we can test this and demonstrate the code's functionality. All right. So with that object created we're going to wire up an event handler which I have highlighted here from lines 5 to 10. And you'll notice that it's marked as async. It's maybe not totally obvious just looking at the code here but this is technically an async void method. So this is again the single situation that we're told it's okay to use async void. Other than this we're told not to use it. However, in this method you'll notice that I'm just kind of marking when we enter it and when we exit it and you'll see that I'm awaiting a task inside of here and it's named task that throws async because if we go check that task out, if I jump to it, you'll see that it just marks that it's entering this task and that it's going to throw a particular exception. So this is our base case that we're going to look at. You'll see that all that I'm doing is putting a try catch around basically raising this event. So when this is raised, what we should hope to see is that we get starting the event handler and that we're going to throw the exception. Ideally, what we would have happen is that we could go catch the exception. This would be what I would call expected behavior. But what we're going to see is that's not the case when you have async void. So if I go ahead and run this, Visual Studio is breaking for us to say, hey, look, an exception is being thrown. So here's the output. You'll see that we have starting first example and then raising our event. So, so far so good. Then we have starting the event handler. So now we've made it to line seven and starting the task that throws a sync which if I scroll down a little bit, we actually made it into the task. Then it throws. So technically what we would probably expect to see happen is we would be coming back from line 15, jumping into line 17 to go catch that exception, having our exception handler run, but it never does. nothing was able to actually catch this exception and then you'll notice that we end up writing first example complete on line 22. So this exception got thrown on this event handler and nothing was able to keep track of it. So in my opinion this is really dangerous and even though we're told that async void is okay to use for event handlers like this and the only time in my opinion it's just still too risky when you can't actually handle exceptions properly. So what can we do about this? So, one of the solutions that I'm going to walk through today is actually making an adjustment to the object that we have and I will show how we can use a different delegate signature to be able to register asynchronous event handlers. So, let's go have a look at this new fancy raising object. You'll notice that one of the big differences here is that I have an async event handler as the type here instead of just event handler. So, this is something I just whipped up literally right above. You can see on line 55 and 56 the signature of this instead of having void here it has task otherwise this is largely the same as the normal event handler signature I've just changed it from void to task and now what's really interesting about that is if we go look at the raise method now it's I've called it raise async you can see that it is also marked as async and we will await between lines 65 and 67 we will await invoking this event we weren't able to do this before because the signature of the event handler and the event itself was marked as void. Now we can await it because we've changed the signature to actually be async task. Okay. So in practice, how does this actually look? You'll see that I'm kind of doing the same thing at the start and the end where I'm just marking that we're starting and ending the example. But otherwise, it's largely set up very much the same as the first example. We just have an event handler. It's still marked as async. But what's again not obvious here just based on the syntax is this is actually an async task now. And that's really cool because in the catch block between line 40 and 43 when we're awaiting our task we should be able to actually catch our exception. That's one of the fundamental differences between async void and async task. We can actually catch the exception. So let's go ahead and run this. Great. So I have the output from running this example pulled up on the screen. Now you'll see that we have starting second example raising our async event. So so far so good. This looks kind of just like the first example. Starting the event handler, starting the task that throws and then the fundamental difference between this example and the first is that we actually get into our exception handler. You'll notice that it says our exception handler caught and then the actual exception that we caught and that's on line 42. Compared to the first example, we do make it into the catch block. So if you're creating classes that you do want to have support for asynchronous events and you can control the signature of those events, this is an opportunity where you could start to introduce in your codebase an async task event handler. So just a super quick video for today, but there's one solution that you could look at implementing if you want asynchronous event handlers and you're able to control the signatures of the events on the classes that you're creating. So, I'd love to hear from you in the comments if you have a different approach that you use to solve this problem. So, thanks again for watching. If you could do me a favor, give the video a thumbs up, subscribe to the channel, and we'll see you next time.

Frequently Asked Questions

What is the main issue with using async void in event handlers?

The main issue with using async void in event handlers is that when an exception is thrown, it cannot be easily caught by the application. This can lead to unexpected behavior and make debugging difficult.

How can I handle exceptions in asynchronous event handlers?

You can handle exceptions in asynchronous event handlers by changing the event handler signature from void to Task. This allows you to await the event handler and properly catch any exceptions that may occur.

What should I do if I want to use async event handlers in my classes?

If you want to use async event handlers in your classes, you should define your event handlers with an async Task signature. This way, you can control the behavior of the event and handle exceptions effectively.

These FAQs were generated by AI from the video transcript.
An error has occurred. This application may no longer respond until reloaded. Reload