Skip to content

Instantly share code, notes, and snippets.

@jimevans
Last active February 28, 2024 22:10
Show Gist options
  • Save jimevans/da633ab80c3d55b7b08fd83890675655 to your computer and use it in GitHub Desktop.
Save jimevans/da633ab80c3d55b7b08fd83890675655 to your computer and use it in GitHub Desktop.
EventHandler Helper Class with Support for Async Events
namespace WebDriverBiDi.Client;
using System.Reflection;
/// <summary>
/// Delegate for asynchronously handling events.
/// </summary>
/// <typeparam name="T">The type of <see cref="EventArgs"/> object containing information about the event.</typeparam>
/// <param name="sender">The sender of the event. This argument is optional and may be <see langword="null"/>.</param>
/// <param name="eventArgs">The <see cref="EventArgs"/> object containing information about the event.</param>
/// <returns>A <see cref="Task"/> object representing the result of the asynchronous operation.</returns>
public delegate Task AsyncEventHandler<T>(object? sender, T eventArgs)
where T : EventArgs;
/// <summary>
/// Creates an object that can respond to events with proper exception handling
/// for both synchronous and asynchronous event handlers.
/// </summary>
/// <typeparam name="T">An EventArgs that contains information about the event.</typeparam>
public class EventResponder<T>
where T : EventArgs
{
private readonly TaskCompletionSource<bool> taskCompletionSource = new();
private readonly EventHandler<T> eventHandler;
private AsyncEventHandler<T> callback;
/// <summary>
/// Initializes a new instance of the <see cref="EventResponder{T}"/> class.
/// </summary>
/// <param name="handler">An EventHandler delegate that handles the event.</param>
public EventResponder(EventHandler<T> handler)
: this(WrapEventHandler(handler))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EventResponder{T}"/> class.
/// </summary>
/// <param name="asyncHandler">An AsyncEventHandler delegate that handles the event asynchronously.</param>
public EventResponder(AsyncEventHandler<T> asyncHandler)
{
this.callback = asyncHandler;
this.eventHandler = this.CreateEventHandler(asyncHandler);
}
/// <summary>
/// Gets the EventHandler delegate to assign to the event.
/// </summary>
public EventHandler<T> Handler => this.eventHandler;
/// <summary>
/// Asynchronously waits for the handler for the event to have completed, errored, or timed out.
/// </summary>
/// <param name="timeout">The TimeSpan to wait for the event handler to have completed.</param>
/// <returns>A Task object containing information about the wait operation.</returns>
/// <exception cref="TaskCanceledException">Thrown when the timeout occurs before the event handler completes.</exception>
public Task WaitForEvent(TimeSpan timeout)
{
bool waitCompleted = this.taskCompletionSource.Task.Wait(timeout);
if (!waitCompleted)
{
this.taskCompletionSource.SetCanceled();
}
return this.taskCompletionSource.Task;
}
private static AsyncEventHandler<T> WrapEventHandler(EventHandler<T> handler)
{
AsyncEventHandler<T> asyncWrapper = (object? sender, T eventArgs) =>
{
handler.Invoke(sender, eventArgs);
return Task.CompletedTask;
};
return asyncWrapper;
}
private EventHandler<T> CreateEventHandler(AsyncEventHandler<T> handler)
{
return new EventHandler<T>(async (object? sender, T eventArgs) =>
{
try
{
await this.callback.Invoke(sender, eventArgs);
this.taskCompletionSource.SetResult(true);
}
catch (TargetInvocationException invocationException) when (invocationException.InnerException is not null)
{
// We want to return the actual exception thrown by the handler method,
// so in the case of a TargetInvocationException, we can return the
// InnerException as the actual exception thrown by the handler.
this.taskCompletionSource.SetException(invocationException.InnerException);
}
catch (Exception e)
{
this.taskCompletionSource.SetException(e);
}
});
}
}
EventResponder<BeforeRequestSentEventArgs> responder = new(async (sender, args) =>
{
if (args.IsBlocked)
{
await bidi.Network.ContinueRequestAsync(new Network.ContinueRequestParameters
{
Request = args.Request.Id,
//Method = "POST"
});
}
};
bidi.Network.BeforeRequestSent += responder.Handler;
EventResponder<LogEntryAddedEventArgs> responder = new((sender, args) =>
{
DoSomethingWithMessage(args.Message);
}
bidi.Log.LogEntryAdded += responder.Handle;
DoSomethingThatWillRaiseLogEntryAddedEvent();
await responder.WaitForEvent(TimeSpan.FromSeconds(1));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment