Last active
February 28, 2024 22:10
-
-
Save jimevans/da633ab80c3d55b7b08fd83890675655 to your computer and use it in GitHub Desktop.
EventHandler Helper Class with Support for Async Events
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
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); | |
} | |
}); | |
} | |
} |
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
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; |
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
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