Moerwald
Moerwald

Reputation: 11274

EventHandler with Task as return value

The base C# EventHandler is defined as:

namespace System
{
    public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
}

Does anyone if there is an awaitable event handler available? E.g.

public delegate Task EventHandlerAsnyc<TEventArgs>(object sender, TEventArgs e);

Thx

Upvotes: 3

Views: 3164

Answers (2)

relatively_random
relatively_random

Reputation: 5146

The downside of just using async void handlers is that there is no way for the caller to wait for the result. This may be an issue for some interactive event handlers, like the ones using CancelEventArgs.

But you can still declare a Task-returning delegate type, if you wish. You just have to be careful how you raise it then. For instance, you could make an extension method which you can call as handler.Raise(sender, EventArgs.Empty).

public delegate Task EventHandlerAsnyc<TEventArgs>(object sender, TEventArgs eventArgs);

public static async Task Raise<TEventArgs>(this EventHandlerAsnyc<TEventArgs> handlers, object sender, TEventArgs eventArgs)
{
    if (handlers == null)
        return;

    foreach (var handler in handlers.GetInvocationList())
        await ((EventHandlerAsnyc<TEventArgs>)handler).Invoke(sender, eventArgs);
}

Alternatively, you can allow handlers to execute concurrently. But this would probably be a bad idea unless well-documented, as it's probably considered surprising behavior.

public static Task RaiseAllowConcurrent<TEventArgs>(this EventHandlerAsnyc<TEventArgs> handlers, object sender, TEventArgs eventArgs)
{
    if (handlers == null)
        return Task.CompletedTask;

    var invocationList = handlers.GetInvocationList();
    var tasks = new Task[invocationList.Length];

    for (var i = 0; i < invocationList.Length; ++i)
        tasks[i] = ((EventHandlerAsnyc<TEventArgs>)invocationList[i]).Invoke(sender, eventArgs);

    return Task.WhenAll(tasks);
}

Upvotes: 3

Ren&#233; Vogt
Ren&#233; Vogt

Reputation: 43896

If you want your event to be processed async (meaning you can use await to return early and resume later) you can simply declare the handler as async void:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponents();

        myButton.Click += myButton_Click;
    }

    public async void myButton_Click(object sender, EventArgs e)
    {
        myButton.Enabled = false;

        await SomeAsyncOrLongRunningOnAnotherThreadTask();

        myButton.Enabled = true;
    }
}

This way SomeAsyncOrLongRunningOnAnotherThreadTask() won't block your UI thread. And the handler is resumed after that task completes.


Side note: normally async methods should always return a Task or Task<T> that can be awaited or otherwise handled by the caller. The use case above is (afaik) the only justified case where void should be used for an async method.

Upvotes: 7

Related Questions