Skip to content

Instantly share code, notes, and snippets.

@Sergio0694
Last active November 25, 2020 04:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sergio0694/d91d59d230aca84dacd082ebb03ed63c to your computer and use it in GitHub Desktop.
Save Sergio0694/d91d59d230aca84dacd082ebb03ed63c to your computer and use it in GitHub Desktop.
A helper to await for events in an asynchronous manner.
/// <summary>
/// A helper to await for events in an asynchronous manner.
/// </summary>
public static class EventAwaiter
{
/// <summary>
/// Awaits a specific event to be raised and executes a callback when that happens.
/// </summary>
/// <typeparam name="THandler">The type of handler to await.</typeparam>
/// <param name="register">An <see cref="Action{T}"/> to register the event.</param>
/// <param name="unregister">An <see cref="Action{T}"/> to unregister the event.</param>
/// <param name="handler">The callback to execute once when the event is raised.</param>
/// <returns>A <see cref="Task"/> that completes when the event is raised.</returns>
/// <remarks>The returned <see cref="Task"/> can be ignored if needed.</remarks>
public static async Task Await<THandler>(
Action<THandler> register,
Action<THandler> unregister,
THandler handler)
where THandler : Delegate
{
var data = PrepareAwait<THandler, object>();
THandler combinedHandler = (THandler)Delegate.Combine(handler, data.Handler);
register(combinedHandler);
await data.Task;
unregister(combinedHandler);
}
/// <summary>
/// Awaits a specific event to be raised.
/// </summary>
/// <typeparam name="THandler">The type of handler to await.</typeparam>
/// <param name="register">An <see cref="Action{T}"/> to register the event.</param>
/// <param name="unregister">An <see cref="Action{T}"/> to unregister the event.</param>
/// <returns>A <see cref="Task"/> that completes when the event is raised.</returns>
public static async Task Await<THandler>(
Action<THandler> register,
Action<THandler> unregister)
where THandler : Delegate
{
var data = PrepareAwait<THandler, object>();
register(data.Handler);
await data.Task;
unregister(data.Handler);
}
/// <summary>
/// Awaits a specific event to be raised.
/// </summary>
/// <typeparam name="THandler">The type of handler to await.</typeparam>
/// <typeparam name="TArgs">The type of args to return.</typeparam>
/// <param name="register">An <see cref="Action{T}"/> to register the event.</param>
/// <param name="unregister">An <see cref="Action{T}"/> to unregister the event.</param>
/// <returns>A <see cref="Task"/> that completes when the event is raised.</returns>
[Pure]
public static async Task<TArgs> Await<THandler, TArgs>(
Action<THandler> register,
Action<THandler> unregister)
where THandler : Delegate
where TArgs : class
{
var data = PrepareAwait<THandler, TArgs>();
register(data.Handler);
TArgs result = await data.Task;
unregister(data.Handler);
return result;
}
/// <summary>
/// Prepares a handler and awaitable task with the specified parameters.
/// </summary>
/// <typeparam name="THandler">The type of handler to prepare.</typeparam>
/// <typeparam name="TArgs">The type of argument to return.</typeparam>
/// <returns>A pair of handler and task to await based on the input parameters.</returns>
[Pure]
private static (THandler Handler, Task<TArgs> Task) PrepareAwait<THandler, TArgs>()
where THandler : Delegate
where TArgs : class
{
TaskCompletionSource<TArgs> tcs = new TaskCompletionSource<TArgs>();
// Prepare the event handler parameters
Type[] args = (
from param in typeof(THandler).GetMethod("Invoke")!.GetParameters()
select param.ParameterType).ToArray();
ParameterExpression[] handlerParameters =
{
Expression.Parameter(args[0], "s"),
Expression.Parameter(args[1], "e")
};
// Capture the tcs and prepare the invocation body
Type tcsType = typeof(TaskCompletionSource<TArgs>);
Expression tcsConstant = Expression.Constant(tcs, tcsType);
MethodInfo trySetResultMethodInfo = tcsType.GetMethod(nameof(TaskCompletionSource<TArgs>.TrySetResult));
Expression[] trySetResultArgs = { handlerParameters[1] };
MethodCallExpression body = Expression.Call(tcsConstant, trySetResultMethodInfo!, trySetResultArgs);
// Compile the handler
THandler handler = Expression.Lambda<THandler>(body, handlerParameters).Compile();
return (handler, tcs.Task);
}
}
class Program
{
static async Task Main()
{
var element = new UIElement();
Console.WriteLine("Registering...");
Task loadedTask = EventAwaiter.Await<EventHandler>(
h => element.Loaded += h,
h => element.Loaded -= h);
Console.WriteLine("Await...");
await Task.Delay(500);
Console.WriteLine("Raising...");
element.Load();
Console.WriteLine("Awaiting...");
await loadedTask;
Console.WriteLine("LOADED!");
Console.ReadKey();
}
}
public class UIElement
{
public event EventHandler Loaded;
public void Load() => Loaded?.Invoke(this, EventArgs.Empty);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment