10

In my code I need to unregister and register an event handler, This works perfect:

_favoritsImageView.Click -= _favoritsImageView_Click(this, new CustomeClickEventArgs(item));
_favoritsImageView.Click += _favoritsImageView_Click(this, new CustomeClickEventArgs(item));
void _favoritsImageView_Click(object sender, CustomeClickEventArgs e)
{
       // handles the event
}

But for an awaitable event handler i have to use this syntax:

_favoritsImageView.Click -= async (s, e) => 
{ await _favoritsImageView_ClickAsync(s, new CustomeClickEventArgs(item)); };

_favoritsImageView.Click += async (s, e) => 
{ await _favoritsImageView_ClickAsync(s, new CustomeClickEventArgs(item)); };

async Task _favoritsImageView_ClickAsync(object sender, CustomeClickEventArgs e)
{
       // does async task
}

This does not work. Because anonymous methods do not have the same reference. So the first line does not un-register the already registered handler. And eventually the second line adds an extra event handlers to the click.

Which syntax do I need to use to add and remove an async event handler?

Thanks for any suggestion.

a.toraby
  • 3,232
  • 5
  • 41
  • 73
  • I'm confused, the syntax that you say "works perfect" can't work. Unless `_favoritsImageView_Click` was a method that returns a delegate, which would be really weird. – svick Nov 25 '15 at 17:51
  • @svick I added the `_favoritsImageView_Click` to the sample code. Whats wrong with that?? – a.toraby Nov 26 '15 at 07:09
  • What's wrong is that the line `_favoritsImageView.Click += _favoritsImageView_Click(this, new CustomeClickEventArgs(item));` won't compile. – svick Nov 26 '15 at 13:00

2 Answers2

10

Which syntax do I need to use to add and remove an async event handler?

The same syntax as you'd need to use with regular event handlers. You need to save the delegate somewhere so you can later de-register it:

private EventHandler eventHandler = 
                            new EventHandler(async (s, e) => await FooAsync(s, e));

public async void SomeOtherEventHandler()
{
    var m = new M();
    m.X += eventHandler;
    m.OnFoo();
    m.X -= eventHandler;
    m.OnFoo();
}

public async Task FooAsync(object sender, EventArgs e)
{
    await Task.Delay(1000);
    Debug.WriteLine("Yay event handler");
}

public class M
{
    public event EventHandler X;
    public void OnX()
    {
        // If you're using C#-6, then X?.Invoke(null, EventArgs.Empty);

        var localX = X;
        if (localX != null)
            localX(null, EventArgs.Empty);
    }
}

Edit:

@svick suggests perhaps another solution, to simply make the method async void and register it directly, which is definitely shorter:

public async void SomeOtherEventHandler()
{
    var m = new M();
    m.X += FooAsync;
    m.OnFoo();
    m.X -= FooAsync;
    m.OnFoo();
}

public async void FooAsync(object sender, EventArgs e)
{
    await Task.Delay(1000);
    Debug.WriteLine("Yay event handler");
}
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • 1
    You only need to save the delegate if your event handler is a lambda. I think that generally, a better solution is to write the event handler as a method (`async void` method in this case), which would make the code simpler. – svick Nov 25 '15 at 17:55
  • @svick That's a good idea. Added another version with the direct registration. – Yuval Itzchakov Nov 25 '15 at 18:01
  • I think it could be great to know what's the difference between a Task return and a void return in this case, and why the @svick answer is good :) https://stackoverflow.com/a/8043882/6400617 – Nicolas Bodin Dec 20 '17 at 16:22
  • That's a solution I was looking for if you are using async methods inside event handlers and you want to enable/disable/replace them – Toolkit Dec 08 '20 at 16:11
6

The synchronous code looks something like this:

private void FavoritsImageView_Click(object sender, CustomeClickEventArgs args)
{
    // your synchronous code here
}

…

_favoritsImageView.Click += FavoritsImageView_Click;
_favoritsImageView.Click -= FavoritsImageView_Click;

The async version looks almost the same, you only need to add async to the method:

private async void FavoritsImageView_Click(object sender, CustomeClickEventArgs args)
{
    // your asynchronous code here
}

…

_favoritsImageView.Click += FavoritsImageView_Click;
_favoritsImageView.Click -= FavoritsImageView_Click;

Note that an event handler is pretty much the only place where you should use async void. In most other situations, a synchronous void method should be converted to async Task method.

svick
  • 236,525
  • 50
  • 385
  • 514
  • I tried it already. Compiler Error: Cannot implicitly convert type 'void' to 'System.EventHandler' – a.toraby Nov 26 '15 at 07:11
  • @a.toraby I get that error when I use your syntax `+= FavoritsImageView_Click(this, new CustomeClickEventArgs(item));`. But that's not what I have suggested. – svick Nov 26 '15 at 13:04
  • @downvoter Care to explain what you think is wrong with my answer? – svick Nov 29 '15 at 13:36