Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to have async methods as callbacks to eventhandlers in c#?

Tags:

c#

async-await

My design is illustrated by below example. Having a while true loop doing something and notifying by an event that it has done something to all subscribers. My application should not continue its execution before its done notifying all subscribers, where this works as long as someone do not put a async void on the callback.

If someone put a async void on the callback to await some task, then my loop can continue before the callback is completed. What other designs can I do to avoid this situation.

Its 3th party plugins that register themeself and subscribe to the event, so I have no control over if they put a async void. Understandable I cant do Task callbacks for the EventHandler, so what alternatives do I have with .net 4.5.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication4
{
    public class Test
    {

        public event EventHandler Event;

        public void DoneSomething()
        {
            if (Event != null)
                Event(this,EventArgs.Empty);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var test = new Test();

            test.Event += test_Event;
            test.Event +=test_Event2;

            while(true)
            {
                test.DoneSomething();
                Thread.Sleep(1000);

            }
        }

        private static void test_Event2(object sender, EventArgs e)
        {
            Console.WriteLine("delegate 2");
        }

        static async void test_Event(object sender, EventArgs e)
        {
            Console.WriteLine("Del1gate 1");

            await Task.Delay(5000);

            Console.WriteLine("5000 ms later");

        }
    }
}
like image 880
Poul K. Sørensen Avatar asked Oct 21 '13 22:10

Poul K. Sørensen


People also ask

Can event handlers be async?

You can register both synchronous and asynchronous event handlers concurrently to the same event. The Event Manager framework executes the registered event handlers in whichever mode you configure them to operate, always executing synchronous event handlers first.

Which async method can be used for event handlers?

NET events do not support async Task as a result type! Instead, you have to cast event handlers as async void if you want to run async code inside of an event handler method and to comply with the classic .

Can you call an async method without await?

In this way, an async function without an await expression will run synchronously. If there is an await expression inside the function body, however, the async function will always complete asynchronously.

What happens if we execute an asynchronous method but do not await it?

If you forget to use await while calling an async function, the function starts executing. This means that await is not required for executing the function. The async function will return a promise, which you can use later.


1 Answers

If someone put a async void on the callback to await some task, then my loop can continue before the callback is completed. What other designs can I do to avoid this situation.

There is really no way to avoid this. Even if you were to somehow "know" that the subscriber wasn't implemented via async/await, you still couldn't guarantee that the caller didn't build some form of asynchronous "operation" in place.

For example, a completely normal void method could put all of its work into a Task.Run call.

My application should not continue its execution before its done notifying all subscribers

Your current version does follow this contract. You're notifying the subscribers synchronously - if a subscriber does something asynchronously in response to that notification, that is something outside of your control.


Understandable I cant do Task callbacks for the EventHandler, so what alternatives do I have with .net 4.5.

Note that this is actually possible. For example, you can rewrite your above as:

public class Program
{
    public static void Main()
    {       
        var test = new Test();

        test.Event += test_Event;
        test.Event +=test_Event2;

        test.DoneSomethingAsync().Wait();
    }
}

public delegate Task CustomEvent(object sender, EventArgs e);

private static Task test_Event2(object sender, EventArgs e)
{
    Console.WriteLine("delegate 2");
    return Task.FromResult(false);
}

static async Task test_Event(object sender, EventArgs e)
{
    Console.WriteLine("Del1gate 1");
    await Task.Delay(5000);
    Console.WriteLine("5000 ms later");
}

public class Test
{
    public event CustomEvent Event;
    public async Task DoneSomethingAsync()
    {
        var handler = this.Event;
        if (handler != null)
        {
              var tasks = handler.GetInvocationList().Cast<CustomEvent>().Select(s => s(this, EventArgs.Empty));
              await Task.WhenAll(tasks);                
        }
    }
}

You can also rewrite this using event add/remove, as suggested by svick:

public class Test
{
    private List<CustomEvent> events = new List<CustomEvent>();
    public event CustomEvent Event
    {
        add { lock(events) events.Add(value); }
        remove { lock(events) events.Remove(value); }
    }

    public async Task DoneSomething()
    {
        List<CustomEvent> handlers;
        lock(events) 
            handlers = this.events.ToList(); // Cache this
        var tasks = handlers.Select(s => s(this, EventArgs.Empty));
        await Task.WhenAll(tasks);
    }
}
like image 166
Reed Copsey Avatar answered Nov 15 '22 00:11

Reed Copsey