Last active
November 25, 2020 04:56
-
-
Save Sergio0694/d91d59d230aca84dacd082ebb03ed63c to your computer and use it in GitHub Desktop.
A helper to await for events in an asynchronous manner.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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