27

I have already read the SO posts here and article here. I have a timer event that fires every once in a while and I want to do some asynchronous processing inside the handler, so something along the lines of:

Timer timer = new Timer();
timer.Interval = 1000;
timer.Elapsed += timer_Elapsed; // Please ignore this line. But some answers already given based on this line so I will leave it as it is.
timer.Elapsed += async (sender, arguments) => await timer_Elapsed(sender, arguments);

private async Task timer_Elapsed(object sender, ElapsedEventArgs e)
{
    await Task.Delay(10);
}

The code above is compiling and working.

But I am not sure why the code is compiling. The ElapsedEventHandler expected signature is

void timer_Elapsed(object sender, ElapsedEventArgs e)

However my method returns Task instead of void since async void is not recommended. but that does not match with ElapsedEventHandler signature and yet it's still compiling and working?

Is it okay to call an async method on Timer.Elapsed? The code will be executed inside a Windows service.

Update 1

async void is "not recommended", with one very important exception: event handlers.

Does it matter if it's an asynchronous event handler or synchronous event hander?

An MSDN article here says:

Void-returning async methods have a specific purpose: to make asynchronous event handlers possible.

Timer.Elapsed is I think a synchronous event handler; can I still attach async void to it?

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
LP13
  • 30,567
  • 53
  • 217
  • 400

2 Answers2

30

async void is "not recommended", with one very important exception: event handlers.

Your code compiles fine (well, the second event subscription…the first would generate a compile-time error, assuming the same timer_Elapsed() method in both statements), because the compiler can infer the delegate's return type should be void. The async anonymous method could also return Task, but in this case that would be the wrong method signature, so you get void instead.

It would also be fine to just declare your event handle as async void:

   private async void timer_Elapsed(object sender, ElapsedEventArgs e)
   {
      await Task.Delay(10);
   }

Used like:

timer.Elapsed += timer_Elapsed;

Returning void for async methods is not ideal, but in the case of an event handler, there is no code that is going to use the Task anyway (unless the event is specifically implemented to understand async methods, as in Asynchronous events in C#). There's no reason to bend over backwards to comply with what would otherwise be the correct coding practice, if you get zero benefit from doing so.


See also Should I avoid 'async void' event handlers?


Addendum:

From your edit to the question:

Timer.Elapsed is I think synchronous event handler can I still attach async void to it?

It's not the event that is asynchronous or synchronous, but the handler itself. And that's determined entirely by whether you use async and await for the handler method. You may, as described in your question and my answer, use an async void handler method with the Elapsed event, just as you may with any other event (assuming the event signature requires void as the handler return type, which is of course the standard for conventional .NET events).

Community
  • 1
  • 1
Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • Thanks Will there be any issue handling exceptions if I used `private async void timer_Elapsed(object sender, ElapsedEventArgs e) ` instead of `private async Task timer_Elapsed(object sender, ElapsedEventArgs e) ` – LP13 Sep 01 '16 at 01:49
  • I guess that depends on what you mean by "issue". It's impossible to say without a complete code example that shows how and what exception is being thrown. But in general, your exception may go unobserved, depending on where the continuation is executed. If it is observed, it could be in a context that would terminate your process. – Peter Duniho Sep 01 '16 at 02:24
  • That said, in your example either it will be observed or it won't be observed, whether you wrap your `async Task` method in an `async void` anonymous method or just have an `async void` event handler method, since in both cases you're ultimately dealing with an event handler method that is `async void` and has `await`ed the operation that threw the exception. I.e. as far as exceptions go, they are equivalent. – Peter Duniho Sep 01 '16 at 02:24
0

You are correct about avoiding "async void". Not sure about the compiler not objecting but you are subscribing to the same event twice.

You should keep the asynchronous part inside the handler, not in the calling of the handler.

Try removing the duplicate event subscription:

timer.Elapsed += async (sender, arguments) => await timer_Elapsed(sender, arguments);

and change the handler's signature from:

private async Task timer_Elapsed(object sender, ElapsedEventArgs e)

to:

private void timer_Elapsed(object sender, ElapsedEventArgs e)

Then do the asynchronous part inside the handler, using something like the suggestion in this post: https://stackoverflow.com/a/31469699/6776481

Except the "WrapSomeMethod" call would return just Task, not Task<string>.

Also, since this is inside of a windows service, don't forget to unsubscribe from the event if necessary.

Community
  • 1
  • 1
mikef
  • 1
  • 2