I am creating a class that has a series of events, one of them being GameShuttingDown. When this event is fired, I need to invoke the event handler. The point of this event is to notify users the game is shutting down and they need to save their data. The saves are awaitable, and events are not. So when the handler gets called, the game shuts down before the awaited handlers can complete.
public event EventHandler<EventArgs> GameShuttingDown;
public virtual async Task ShutdownGame()
{
await this.NotifyGameShuttingDown();
await this.SaveWorlds();
this.NotifyGameShutDown();
}
private async Task SaveWorlds()
{
foreach (DefaultWorld world in this.Worlds)
{
await this.worldService.SaveWorld(world);
}
}
protected virtual void NotifyGameShuttingDown()
{
var handler = this.GameShuttingDown;
if (handler == null)
{
return;
}
handler(this, new EventArgs());
}
Event registration
// The game gets shut down before this completes because of the nature of how events work
DefaultGame.GameShuttingDown += async (sender, args) => await this.repo.Save(blah);
I understand that the signature for events are void EventName and so making it async is basically fire and forget. My engine makes heavy use of eventing to notify 3rd party developers (and multiple internal components) that events are taking place within the engine and letting them react to them.
Is there a good route to go down to replace eventing with something asynchronous based that I can use? I'm not sure if I should be using BeginShutdownGame and EndShutdownGame with callbacks, but that's a pain because then only the calling source can pass a callback, and not any 3rd party stuff that plugs in to the engine, which is what I am getting with events. If the server calls game.ShutdownGame(), there's no way for engine plugins and or other components within the engine to pass along their callbacks, unless I wire up some kind of registration method, keeping a collection of callbacks.
Any advice on what the preferred/recommended route to go down with this would be greatly appreciated! I have looked around and for the most part what I've seen is using the Begin/End approach which I don't think will satisfy what I'm wanting to do.
Edit
Another option I'm considering is using a registration method, that takes an awaitable callback. I iterate over all of the callbacks, grab their Task and await with a WhenAll.
private List<Func<Task>> ShutdownCallbacks = new List<Func<Task>>();
public void RegisterShutdownCallback(Func<Task> callback)
{
this.ShutdownCallbacks.Add(callback);
}
public async Task Shutdown()
{
var callbackTasks = new List<Task>();
foreach(var callback in this.ShutdownCallbacks)
{
callbackTasks.Add(callback());
}
await Task.WhenAll(callbackTasks);
}
Personally, I think that having async event handlers may not be the best design choice, not the least of which reason being the very problem you're having. With synchronous handlers, it's trivial to know when they complete.
That said, if for some reason you must or at least are strongly compelled to stick with this design, you can do it in an await-friendly way.
Your idea to register handlers and await them is a good one. However, I would suggest sticking with the existing event paradigm, as that will keep the expressiveness of events in your code. The main thing is that you have to deviate from the standard EventHandler-based delegate type, and use a delegate type that returns a Task so that you can await the handlers.
Here's a simple example illustrating what I mean:
class A
{
public event Func<object, EventArgs, Task> Shutdown;
public async Task OnShutdown()
{
Func<object, EventArgs, Task> handler = Shutdown;
if (handler == null)
{
return;
}
Delegate[] invocationList = handler.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, EventArgs, Task>)invocationList[i])(this, EventArgs.Empty);
}
await Task.WhenAll(handlerTasks);
}
}
The OnShutdown() method, after doing the standard "get local copy of the event delegate instance", first invokes all of the handlers, and then awaits all of the returned Tasks (having saved them to a local array as the handlers are invoked).
Here's a short console program illustrating the use:
class Program
{
static void Main(string[] args)
{
A a = new A();
a.Shutdown += Handler1;
a.Shutdown += Handler2;
a.Shutdown += Handler3;
a.OnShutdown().Wait();
}
static async Task Handler1(object sender, EventArgs e)
{
Console.WriteLine("Starting shutdown handler #1");
await Task.Delay(1000);
Console.WriteLine("Done with shutdown handler #1");
}
static async Task Handler2(object sender, EventArgs e)
{
Console.WriteLine("Starting shutdown handler #2");
await Task.Delay(5000);
Console.WriteLine("Done with shutdown handler #2");
}
static async Task Handler3(object sender, EventArgs e)
{
Console.WriteLine("Starting shutdown handler #3");
await Task.Delay(2000);
Console.WriteLine("Done with shutdown handler #3");
}
}
Having gone through this example, I now find myself wondering if there couldn't have been a way for C# to abstract this a bit. Maybe it would have been too complicated a change, but the current mix of the old-style void-returning event handlers and the new async/await feature does seem a bit awkward. The above works (and works well, IMHO), but it would have been nice to have better CLR and/or language support for the scenario (i.e. be able to await a multicast delegate and have the C# compiler turn that into a call to WhenAll()).
Peter's example is great, I've just simplified it a little using LINQ and extensions:
public static class AsynchronousEventExtensions
{
public static Task Raise<TSource, TEventArgs>(this Func<TSource, TEventArgs, Task> handlers, TSource source, TEventArgs args)
where TEventArgs : EventArgs
{
if (handlers != null)
{
return Task.WhenAll(handlers.GetInvocationList()
.OfType<Func<TSource, TEventArgs, Task>>()
.Select(h => h(source, args)));
}
return Task.CompletedTask;
}
}
It may be a good idea to add a timeout. To raise the event call Raise extension:
public event Func<A, EventArgs, Task> Shutdown;
private async Task SomeMethod()
{
...
await Shutdown.Raise(this, EventArgs.Empty);
...
}
But you have to be aware that, unlike synchronous evens, this implementation calls handlers concurrently. It can be an issue if handlers have to be executed strictly consecutively what they are often do, e.g. a next handler depends on results of the previous one:
someInstance.Shutdown += OnShutdown1;
someInstance.Shutdown += OnShutdown2;
...
private async Task OnShutdown1(SomeClass source, MyEventArgs args)
{
if (!args.IsProcessed)
{
// An operation
await Task.Delay(123);
args.IsProcessed = true;
}
}
private async Task OnShutdown2(SomeClass source, MyEventArgs args)
{
// OnShutdown2 will start execution the moment OnShutdown1 hits await
// and will proceed to the operation, which is not the desired behavior.
// Or it can be just a concurrent DB query using the same connection
// which can result in an exception thrown base on the provider
// and connection string options
if (!args.IsProcessed)
{
// An operation
await Task.Delay(123);
args.IsProcessed = true;
}
}
You'd better change the extension method to call handlers consecutively:
public static class AsynchronousEventExtensions
{
public static async Task Raise<TSource, TEventArgs>(this Func<TSource, TEventArgs, Task> handlers, TSource source, TEventArgs args)
where TEventArgs : EventArgs
{
if (handlers != null)
{
foreach (Func<TSource, TEventArgs, Task> handler in handlers.GetInvocationList())
{
await handler(source, args);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Example
{
// delegate as alternative standard EventHandler
public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e, CancellationToken token);
public class ExampleObject
{
// use as regular event field
public event AsyncEventHandler<EventArgs> AsyncEvent;
// invoke using the extension method
public async Task InvokeEventAsync(CancellationToken token) {
await this.AsyncEvent.InvokeAsync(this, EventArgs.Empty, token);
}
// subscribe (add a listener) with regular syntax
public static async Task UsageAsync() {
var item = new ExampleObject();
item.AsyncEvent += (sender, e, token) => Task.CompletedTask;
await item.InvokeEventAsync(CancellationToken.None);
}
}
public static class AsynEventHandlerExtensions
{
// invoke a async event (with null-checking)
public static async Task InvokeAsync<TEventArgs>(this AsyncEventHandler<TEventArgs> handler, object sender, TEventArgs args, CancellationToken token) {
var delegates = handler?.GetInvocationList();
if (delegates?.Length > 0) {
var tasks = delegates
.Cast<AsyncEventHandler<TEventArgs>>()
.Select(e => e.Invoke(sender, args, token));
await Task.WhenAll(tasks);
}
}
}
}
internal static class EventExtensions
{
public static void InvokeAsync<TEventArgs>(this EventHandler<TEventArgs> #event, object sender,
TEventArgs args, AsyncCallback ar, object userObject = null)
where TEventArgs : class
{
var listeners = #event.GetInvocationList();
foreach (var t in listeners)
{
var handler = (EventHandler<TEventArgs>) t;
handler.BeginInvoke(sender, args, ar, userObject);
}
}
}
example:
public event EventHandler<CodeGenEventArgs> CodeGenClick;
private void CodeGenClickAsync(CodeGenEventArgs args)
{
CodeGenClick.InvokeAsync(this, args, ar =>
{
InvokeUI(() =>
{
if (args.Code.IsNotNullOrEmpty())
{
var oldValue = (string) gv.GetRowCellValue(gv.FocusedRowHandle, nameof(License.Code));
if (oldValue != args.Code)
gv.SetRowCellValue(gv.FocusedRowHandle, nameof(License.Code), args.Code);
}
});
});
}
Note: This is async so the event handler may compromise the UI thread. The event handler (subscriber) should do no UI-work.
It wouldn't make much sense otherwise.
declare your event in your event provider:
public event EventHandler DoSomething;
Invoke event your provider:
DoSomething.InvokeAsync(new MyEventArgs(), this, ar => { callback called when finished (synchronize UI when needed here!) }, null);
subscribe the event by client as you would normally do
It's true, events are inherently un-awaitable so you'll have to work around it.
One solution I have used in the past is using a semaphore to wait for all entries in it to be released. In my situation I only had one subscribed event so I could hardcode it as new SemaphoreSlim(0, 1) but in your case you might want to override the getter/setter for your event and keep a counter of how many subscribers there are so you can dynamically set the max amount of simultaneous threads.
Afterwards you pass a semaphore entry to each of the subscribers and let them do their thing until SemaphoreSlim.CurrentCount == amountOfSubscribers (aka: all spots have been freed).
This would essentially block your program until all event subscribers have finished.
You might also want to consider providing an event à la GameShutDownFinished for your subscribers, which they have to call when they're done with their end-of-game task. Combined with the SemaphoreSlim.Release(int) overload you can now clear up all semaphore entries and simply use Semaphore.Wait() to block the thread. Instead of having to check whether or not all entries have been cleared you now wait until one spot has been freed (but there should only one moment where all spots are freed at once).
I know that the op was asking specifically about using async and tasks for this, but here is an alternative that means the handlers do not need to return a value. The code is based on Peter Duniho's example. First the equivalent class A (squashed up a bit to fit) :-
class A
{
public delegate void ShutdownEventHandler(EventArgs e);
public event ShutdownEventHandler ShutdownEvent;
public void OnShutdownEvent(EventArgs e)
{
ShutdownEventHandler handler = ShutdownEvent;
if (handler == null) { return; }
Delegate[] invocationList = handler.GetInvocationList();
Parallel.ForEach<Delegate>(invocationList,
(hndler) => { ((ShutdownEventHandler)hndler)(e); });
}
}
A simple console application to show its use...
using System;
using System.Threading;
using System.Threading.Tasks;
...
class Program
{
static void Main(string[] args)
{
A a = new A();
a.ShutdownEvent += Handler1;
a.ShutdownEvent += Handler2;
a.ShutdownEvent += Handler3;
a.OnShutdownEvent(new EventArgs());
Console.WriteLine("Handlers should all be done now.");
Console.ReadKey();
}
static void handlerCore( int id, int offset, int num )
{
Console.WriteLine("Starting shutdown handler #{0}", id);
int step = 200;
Thread.Sleep(offset);
for( int i = 0; i < num; i += step)
{
Thread.Sleep(step);
Console.WriteLine("...Handler #{0} working - {1}/{2}", id, i, num);
}
Console.WriteLine("Done with shutdown handler #{0}", id);
}
static void Handler1(EventArgs e) { handlerCore(1, 7, 5000); }
static void Handler2(EventArgs e) { handlerCore(2, 5, 3000); }
static void Handler3(EventArgs e) { handlerCore(3, 3, 1000); }
}
I hope that this is useful to someone.
If you need to await a standard .net event handler you can't do that, because it's void.
But you can create an async event system to handle that:
public delegate Task AsyncEventHandler(AsyncEventArgs e);
public class AsyncEventArgs : System.EventArgs
{
public bool Handled { get; set; }
}
public class AsyncEvent
{
private string name;
private List<AsyncEventHandler> handlers;
private Action<string, Exception> errorHandler;
public AsyncEvent(string name, Action<string, Exception> errorHandler)
{
this.name = name;
this.handlers = new List<AsyncEventHandler>();
this.errorHandler = errorHandler;
}
public void Register(AsyncEventHandler handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this.handlers)
this.handlers.Add(handler);
}
public void Unregister(AsyncEventHandler handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this.handlers)
this.handlers.Remove(handler);
}
public IReadOnlyList<AsyncEventHandler> Handlers
{
get
{
var temp = default(AsyncEventHandler[]);
lock (this.handlers)
temp = this.handlers.ToArray();
return temp.ToList().AsReadOnly();
}
}
public async Task InvokeAsync()
{
var ev = new AsyncEventArgs();
var exceptions = new List<Exception>();
foreach (var handler in this.Handlers)
{
try
{
await handler(ev).ConfigureAwait(false);
if (ev.Handled)
break;
}
catch(Exception ex)
{
exceptions.Add(ex);
}
}
if (exceptions.Any())
this.errorHandler?.Invoke(this.name, new AggregateException(exceptions));
}
}
And you can declare now your async events:
public class MyGame
{
private AsyncEvent _gameShuttingDown;
public event AsyncEventHandler GameShuttingDown
{
add => this._gameShuttingDown.Register(value);
remove => this._gameShuttingDown.Unregister(value);
}
void ErrorHandler(string name, Exception ex)
{
// handle event error.
}
public MyGame()
{
this._gameShuttingDown = new AsyncEvent("GAME_SHUTTING_DOWN", this.ErrorHandler);.
}
}
And invoke your async event using:
internal async Task NotifyGameShuttingDownAsync()
{
await this._gameShuttingDown.InvokeAsync().ConfigureAwait(false);
}
Generic version:
public delegate Task AsyncEventHandler<in T>(T e) where T : AsyncEventArgs;
public class AsyncEvent<T> where T : AsyncEventArgs
{
private string name;
private List<AsyncEventHandler<T>> handlers;
private Action<string, Exception> errorHandler;
public AsyncEvent(string name, Action<string, Exception> errorHandler)
{
this.name = name;
this.handlers = new List<AsyncEventHandler<T>>();
this.errorHandler = errorHandler;
}
public void Register(AsyncEventHandler<T> handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this.handlers)
this.handlers.Add(handler);
}
public void Unregister(AsyncEventHandler<T> handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this.handlers)
this.handlers.Remove(handler);
}
public IReadOnlyList<AsyncEventHandler<T>> Handlers
{
get
{
var temp = default(AsyncEventHandler<T>[]);
lock (this.handlers)
temp = this.handlers.ToArray();
return temp.ToList().AsReadOnly();
}
}
public async Task InvokeAsync(T ev)
{
var exceptions = new List<Exception>();
foreach (var handler in this.Handlers)
{
try
{
await handler(ev).ConfigureAwait(false);
if (ev.Handled)
break;
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
if (exceptions.Any())
this.errorHandler?.Invoke(this.name, new AggregateException(exceptions));
}
}
Related
In my C#/XAML metro app, there's a button which kicks off a long-running process. So, as recommended, I'm using async/await to make sure the UI thread doesn't get blocked:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
await GetResults();
}
private async Task GetResults()
{
// Do lot of complex stuff that takes a long time
// (e.g. contact some web services)
...
}
Occasionally, the stuff happening within GetResults would require additional user input before it can continue. For simplicity, let's say the user just has to click a "continue" button.
My question is: how can I suspend the execution of GetResults in such a way that it awaits an event such as the click of another button?
Here's an ugly way to achieve what I'm looking for: the event handler for the continue" button sets a flag...
private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
_continue = true;
}
... and GetResults periodically polls it:
buttonContinue.Visibility = Visibility.Visible;
while (!_continue) await Task.Delay(100); // poll _continue every 100ms
buttonContinue.Visibility = Visibility.Collapsed;
The polling is clearly terrible (busy waiting / waste of cycles) and I'm looking for something event-based.
Any ideas?
Btw in this simplified example, one solution would be of course to split up GetResults() into two parts, invoke the first part from the start button and the second part from the continue button. In reality, the stuff happening in GetResults is more complex and different types of user input can be required at different points within the execution. So breaking up the logic into multiple methods would be non-trivial.
You can use an instance of the SemaphoreSlim Class as a signal:
private SemaphoreSlim signal = new SemaphoreSlim(0, 1);
// set signal in event
signal.Release();
// wait for signal somewhere else
await signal.WaitAsync();
Alternatively, you can use an instance of the TaskCompletionSource<T> Class to create a Task<T> that represents the result of the button click:
private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
// complete task in event
tcs.SetResult(true);
// wait for task somewhere else
await tcs.Task;
When you have an unusual thing you need to await on, the easiest answer is often TaskCompletionSource (or some async-enabled primitive based on TaskCompletionSource).
In this case, your need is quite simple, so you can just use TaskCompletionSource directly:
private TaskCompletionSource<object> continueClicked;
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
// Note: You probably want to disable this button while "in progress" so the
// user can't click it twice.
await GetResults();
// And re-enable the button here, possibly in a finally block.
}
private async Task GetResults()
{
// Do lot of complex stuff that takes a long time
// (e.g. contact some web services)
// Wait for the user to click Continue.
continueClicked = new TaskCompletionSource<object>();
buttonContinue.Visibility = Visibility.Visible;
await continueClicked.Task;
buttonContinue.Visibility = Visibility.Collapsed;
// More work...
}
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
if (continueClicked != null)
continueClicked.TrySetResult(null);
}
Logically, TaskCompletionSource is like an async ManualResetEvent, except that you can only "set" the event once and the event can have a "result" (in this case, we're not using it, so we just set the result to null).
Here is a utility class that I use:
public class AsyncEventListener
{
private readonly Func<bool> _predicate;
public AsyncEventListener() : this(() => true)
{
}
public AsyncEventListener(Func<bool> predicate)
{
_predicate = predicate;
Successfully = new Task(() => { });
}
public void Listen(object sender, EventArgs eventArgs)
{
if (!Successfully.IsCompleted && _predicate.Invoke())
{
Successfully.RunSynchronously();
}
}
public Task Successfully { get; }
}
And here is how I use it:
var itChanged = new AsyncEventListener();
someObject.PropertyChanged += itChanged.Listen;
// ... make it change ...
await itChanged.Successfully;
someObject.PropertyChanged -= itChanged.Listen;
Ideally, you don't. While you certainly can block the async thread, that's a waste of resources, and not ideal.
Consider the canonical example where the user goes to lunch while the button is waiting to be clicked.
If you have halted your asynchronous code while waiting for the input from the user, then it's just wasting resources while that thread is paused.
That said, it's better if in your asynchronous operation, you set the state that you need to maintain to the point where the button is enabled and you're "waiting" on a click. At that point, your GetResults method stops.
Then, when the button is clicked, based on the state that you have stored, you start another asynchronous task to continue the work.
Because the SynchronizationContext will be captured in the event handler that calls GetResults (the compiler will do this as a result of using the await keyword being used, and the fact that SynchronizationContext.Current should be non-null, given you are in a UI application), you can use async/await like so:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
await GetResults();
// Show dialog/UI element. This code has been marshaled
// back to the UI thread because the SynchronizationContext
// was captured behind the scenes when
// await was called on the previous line.
...
// Check continue, if true, then continue with another async task.
if (_continue) await ContinueToGetResultsAsync();
}
private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
_continue = true;
}
private async Task GetResults()
{
// Do lot of complex stuff that takes a long time
// (e.g. contact some web services)
...
}
ContinueToGetResultsAsync is the method that continues to get the results in the event that your button is pushed. If your button is not pushed, then your event handler does nothing.
Simple Helper Class:
public class EventAwaiter<TEventArgs>
{
private readonly TaskCompletionSource<TEventArgs> _eventArrived = new TaskCompletionSource<TEventArgs>();
private readonly Action<EventHandler<TEventArgs>> _unsubscribe;
public EventAwaiter(Action<EventHandler<TEventArgs>> subscribe, Action<EventHandler<TEventArgs>> unsubscribe)
{
subscribe(Subscription);
_unsubscribe = unsubscribe;
}
public Task<TEventArgs> Task => _eventArrived.Task;
private EventHandler<TEventArgs> Subscription => (s, e) =>
{
_eventArrived.TrySetResult(e);
_unsubscribe(Subscription);
};
}
Usage:
var valueChangedEventAwaiter = new EventAwaiter<YourEventArgs>(
h => example.YourEvent += h,
h => example.YourEvent -= h);
await valueChangedEventAwaiter.Task;
Stephen Toub published this AsyncManualResetEvent class on his blog.
public class AsyncManualResetEvent
{
private volatile TaskCompletionSource<bool> m_tcs = new TaskCompletionSource<bool>();
public Task WaitAsync() { return m_tcs.Task; }
public void Set()
{
var tcs = m_tcs;
Task.Factory.StartNew(s => ((TaskCompletionSource<bool>)s).TrySetResult(true),
tcs, CancellationToken.None, TaskCreationOptions.PreferFairness, TaskScheduler.Default);
tcs.Task.Wait();
}
public void Reset()
{
while (true)
{
var tcs = m_tcs;
if (!tcs.Task.IsCompleted ||
Interlocked.CompareExchange(ref m_tcs, new TaskCompletionSource<bool>(), tcs) == tcs)
return;
}
}
}
With Reactive Extensions (Rx.Net)
var eventObservable = Observable
.FromEventPattern<EventArgs>(
h => example.YourEvent += h,
h => example.YourEvent -= h);
var res = await eventObservable.FirstAsync();
You can add Rx with Nuget Package System.Reactive
Tested Sample:
private static event EventHandler<EventArgs> _testEvent;
private static async Task Main()
{
var eventObservable = Observable
.FromEventPattern<EventArgs>(
h => _testEvent += h,
h => _testEvent -= h);
Task.Delay(5000).ContinueWith(_ => _testEvent?.Invoke(null, new EventArgs()));
var res = await eventObservable.FirstAsync();
Console.WriteLine("Event got fired");
}
I'm using my own AsyncEvent class for awaitable events.
public delegate Task AsyncEventHandler<T>(object sender, T args) where T : EventArgs;
public class AsyncEvent : AsyncEvent<EventArgs>
{
public AsyncEvent() : base()
{
}
}
public class AsyncEvent<T> where T : EventArgs
{
private readonly HashSet<AsyncEventHandler<T>> _handlers;
public AsyncEvent()
{
_handlers = new HashSet<AsyncEventHandler<T>>();
}
public void Add(AsyncEventHandler<T> handler)
{
_handlers.Add(handler);
}
public void Remove(AsyncEventHandler<T> handler)
{
_handlers.Remove(handler);
}
public async Task InvokeAsync(object sender, T args)
{
foreach (var handler in _handlers)
{
await handler(sender, args);
}
}
public static AsyncEvent<T> operator+(AsyncEvent<T> left, AsyncEventHandler<T> right)
{
var result = left ?? new AsyncEvent<T>();
result.Add(right);
return result;
}
public static AsyncEvent<T> operator-(AsyncEvent<T> left, AsyncEventHandler<T> right)
{
left.Remove(right);
return left;
}
}
To declare an event in the class that raises events:
public AsyncEvent MyNormalEvent;
public AsyncEvent<ProgressEventArgs> MyCustomEvent;
To raise the events:
if (MyNormalEvent != null) await MyNormalEvent.InvokeAsync(this, new EventArgs());
if (MyCustomEvent != null) await MyCustomEvent.InvokeAsync(this, new ProgressEventArgs());
To subscribe to the events:
MyControl.Click += async (sender, args) => {
// await...
}
MyControl.Click += (sender, args) => {
// synchronous code
return Task.CompletedTask;
}
Here is a small toolbox of six methods, that can be used for converting events to tasks:
/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on <see cref="EventHandler"/>, to a Task.</summary>
public static Task EventToAsync(
Action<EventHandler> addHandler,
Action<EventHandler> removeHandler)
{
var tcs = new TaskCompletionSource<object>();
addHandler(Handler);
return tcs.Task;
void Handler(object sender, EventArgs e)
{
removeHandler(Handler);
tcs.SetResult(null);
}
}
/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on <see cref="EventHandler{TEventArgs}"/>, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TEventArgs>(
Action<EventHandler<TEventArgs>> addHandler,
Action<EventHandler<TEventArgs>> removeHandler)
{
var tcs = new TaskCompletionSource<TEventArgs>();
addHandler(Handler);
return tcs.Task;
void Handler(object sender, TEventArgs e)
{
removeHandler(Handler);
tcs.SetResult(e);
}
}
/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on a supplied event delegate type, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TDelegate, TEventArgs>(
Action<TDelegate> addHandler, Action<TDelegate> removeHandler)
{
var tcs = new TaskCompletionSource<TEventArgs>();
TDelegate handler = default;
Action<object, TEventArgs> genericHandler = (sender, e) =>
{
removeHandler(handler);
tcs.SetResult(e);
};
handler = (TDelegate)(object)genericHandler.GetType().GetMethod("Invoke")
.CreateDelegate(typeof(TDelegate), genericHandler);
addHandler(handler);
return tcs.Task;
}
/// <summary>Converts a named .NET event, conforming to the standard .NET event
/// pattern based on <see cref="EventHandler"/>, to a Task.</summary>
public static Task EventToAsync(object target, string eventName)
{
var type = target.GetType();
var eventInfo = type.GetEvent(eventName);
if (eventInfo == null) throw new InvalidOperationException("Event not found.");
var tcs = new TaskCompletionSource<object>();
EventHandler handler = default;
handler = new EventHandler((sender, e) =>
{
eventInfo.RemoveEventHandler(target, handler);
tcs.SetResult(null);
});
eventInfo.AddEventHandler(target, handler);
return tcs.Task;
}
/// <summary>Converts a named .NET event, conforming to the standard .NET event
/// pattern based on <see cref="EventHandler{TEventArgs}"/>, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TEventArgs>(
object target, string eventName)
{
var type = target.GetType();
var eventInfo = type.GetEvent(eventName);
if (eventInfo == null) throw new InvalidOperationException("Event not found.");
var tcs = new TaskCompletionSource<TEventArgs>();
EventHandler<TEventArgs> handler = default;
handler = new EventHandler<TEventArgs>((sender, e) =>
{
eventInfo.RemoveEventHandler(target, handler);
tcs.SetResult(e);
});
eventInfo.AddEventHandler(target, handler);
return tcs.Task;
}
/// <summary>Converts a generic Action-based .NET event to a Task.</summary>
public static Task<TArgument> EventActionToAsync<TArgument>(
Action<Action<TArgument>> addHandler,
Action<Action<TArgument>> removeHandler)
{
var tcs = new TaskCompletionSource<TArgument>();
addHandler(Handler);
return tcs.Task;
void Handler(TArgument arg)
{
removeHandler(Handler);
tcs.SetResult(arg);
}
}
All these methods are creating a Task that will complete with the next invocation of the associated event. This task can never become faulted or canceled, it may only complete successfully.
Usage example with a standard event (Progress<T>.ProgressChanged):
var p = new Progress<int>();
//...
int result = await EventToAsync<int>(
h => p.ProgressChanged += h, h => p.ProgressChanged -= h);
// ...or...
int result = await EventToAsync<EventHandler<int>, int>(
h => p.ProgressChanged += h, h => p.ProgressChanged -= h);
// ...or...
int result = await EventToAsync<int>(p, "ProgressChanged");
Usage example with a non-standard event:
public static event Action<int> MyEvent;
//...
int result = await EventActionToAsync<int>(h => MyEvent += h, h => MyEvent -= h);
The event is unsubscribed when the task is completed. No mechanism is provided for unsubscribing earlier than that.
Here is a class I used for testing, which support CancellationToken.
This Test method shows us awaiting an instance of ClassWithEvent's MyEvent to be raised. :
public async Task TestEventAwaiter()
{
var cls = new ClassWithEvent();
Task<bool> isRaisedTask = EventAwaiter<ClassWithEvent>.RunAsync(
cls,
nameof(ClassWithEvent.MyMethodEvent),
TimeSpan.FromSeconds(3));
cls.Raise();
Assert.IsTrue(await isRaisedTask);
isRaisedTask = EventAwaiter<ClassWithEvent>.RunAsync(
cls,
nameof(ClassWithEvent.MyMethodEvent),
TimeSpan.FromSeconds(1));
System.Threading.Thread.Sleep(2000);
Assert.IsFalse(await isRaisedTask);
}
Here's the event awaiter class.
public class EventAwaiter<TOwner>
{
private readonly TOwner_owner;
private readonly string _eventName;
private readonly TaskCompletionSource<bool> _taskCompletionSource;
private readonly CancellationTokenSource _elapsedCancellationTokenSource;
private readonly CancellationTokenSource _linkedCancellationTokenSource;
private readonly CancellationToken _activeCancellationToken;
private Delegate _localHookDelegate;
private EventInfo _eventInfo;
public static Task<bool> RunAsync(
TOwner owner,
string eventName,
TimeSpan timeout,
CancellationToken? cancellationToken = null)
{
return (new EventAwaiter<TOwner>(owner, eventName, timeout, cancellationToken)).RunAsync(timeout);
}
private EventAwaiter(
TOwner owner,
string eventName,
TimeSpan timeout,
CancellationToken? cancellationToken = null)
{
if (owner == null) throw new TypeInitializationException(this.GetType().FullName, new ArgumentNullException(nameof(owner)));
if (eventName == null) throw new TypeInitializationException(this.GetType().FullName, new ArgumentNullException(nameof(eventName)));
_owner = owner;
_eventName = eventName;
_taskCompletionSource = new TaskCompletionSource<bool>();
_elapsedCancellationTokenSource = new CancellationTokenSource();
_linkedCancellationTokenSource =
cancellationToken == null
? null
: CancellationTokenSource.CreateLinkedTokenSource(_elapsedCancellationTokenSource.Token, cancellationToken.Value);
_activeCancellationToken = (_linkedCancellationTokenSource ?? _elapsedCancellationTokenSource).Token;
_eventInfo = typeof(TOwner).GetEvent(_eventName);
Type eventHandlerType = _eventInfo.EventHandlerType;
MethodInfo invokeMethodInfo = eventHandlerType.GetMethod("Invoke");
var parameterTypes = Enumerable.Repeat(this.GetType(),1).Concat(invokeMethodInfo.GetParameters().Select(p => p.ParameterType)).ToArray();
DynamicMethod eventRedirectorMethod = new DynamicMethod("EventRedirect", typeof(void), parameterTypes);
ILGenerator generator = eventRedirectorMethod.GetILGenerator();
generator.Emit(OpCodes.Nop);
generator.Emit(OpCodes.Ldarg_0);
generator.EmitCall(OpCodes.Call, this.GetType().GetMethod(nameof(OnEventRaised),BindingFlags.Public | BindingFlags.Instance), null);
generator.Emit(OpCodes.Ret);
_localHookDelegate = eventRedirectorMethod.CreateDelegate(eventHandlerType,this);
}
private void AddHandler()
{
_eventInfo.AddEventHandler(_owner, _localHookDelegate);
}
private void RemoveHandler()
{
_eventInfo.RemoveEventHandler(_owner, _localHookDelegate);
}
private Task<bool> RunAsync(TimeSpan timeout)
{
AddHandler();
Task.Delay(timeout, _activeCancellationToken).
ContinueWith(TimeOutTaskCompleted);
return _taskCompletionSource.Task;
}
private void TimeOutTaskCompleted(Task tsk)
{
RemoveHandler();
if (_elapsedCancellationTokenSource.IsCancellationRequested) return;
if (_linkedCancellationTokenSource?.IsCancellationRequested == true)
SetResult(TaskResult.Cancelled);
else if (!_taskCompletionSource.Task.IsCompleted)
SetResult(TaskResult.Failed);
}
public void OnEventRaised()
{
RemoveHandler();
if (_taskCompletionSource.Task.IsCompleted)
{
if (!_elapsedCancellationTokenSource.IsCancellationRequested)
_elapsedCancellationTokenSource?.Cancel(false);
}
else
{
if (!_elapsedCancellationTokenSource.IsCancellationRequested)
_elapsedCancellationTokenSource?.Cancel(false);
SetResult(TaskResult.Success);
}
}
enum TaskResult { Failed, Success, Cancelled }
private void SetResult(TaskResult result)
{
if (result == TaskResult.Success)
_taskCompletionSource.SetResult(true);
else if (result == TaskResult.Failed)
_taskCompletionSource.SetResult(false);
else if (result == TaskResult.Cancelled)
_taskCompletionSource.SetCanceled();
Dispose();
}
public void Dispose()
{
RemoveHandler();
_elapsedCancellationTokenSource?.Dispose();
_linkedCancellationTokenSource?.Dispose();
}
}
It basically relies on CancellationTokenSource to report back the result.
It uses some IL injection to create a delegate to match the event's signature.
That delegate is then added as a handler for that event using some reflection.
The body of the generate method simply calls another function on the EventAwaiter class, which then reports success using the CancellationTokenSource.
Caution, do not use this, as is, in product. This is meant as a working example.
For instance, IL generation is an expensive process. You should avoid regenerate the same method over and over again, and instead cache these.
AsyncEx has AsyncManualResetEvent for this. You can:
var signal = new AsyncManualResetEvent();
await signal.WaitAsync();
And trigger it with:
signal.Set();
In my C#/XAML metro app, there's a button which kicks off a long-running process. So, as recommended, I'm using async/await to make sure the UI thread doesn't get blocked:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
await GetResults();
}
private async Task GetResults()
{
// Do lot of complex stuff that takes a long time
// (e.g. contact some web services)
...
}
Occasionally, the stuff happening within GetResults would require additional user input before it can continue. For simplicity, let's say the user just has to click a "continue" button.
My question is: how can I suspend the execution of GetResults in such a way that it awaits an event such as the click of another button?
Here's an ugly way to achieve what I'm looking for: the event handler for the continue" button sets a flag...
private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
_continue = true;
}
... and GetResults periodically polls it:
buttonContinue.Visibility = Visibility.Visible;
while (!_continue) await Task.Delay(100); // poll _continue every 100ms
buttonContinue.Visibility = Visibility.Collapsed;
The polling is clearly terrible (busy waiting / waste of cycles) and I'm looking for something event-based.
Any ideas?
Btw in this simplified example, one solution would be of course to split up GetResults() into two parts, invoke the first part from the start button and the second part from the continue button. In reality, the stuff happening in GetResults is more complex and different types of user input can be required at different points within the execution. So breaking up the logic into multiple methods would be non-trivial.
You can use an instance of the SemaphoreSlim Class as a signal:
private SemaphoreSlim signal = new SemaphoreSlim(0, 1);
// set signal in event
signal.Release();
// wait for signal somewhere else
await signal.WaitAsync();
Alternatively, you can use an instance of the TaskCompletionSource<T> Class to create a Task<T> that represents the result of the button click:
private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
// complete task in event
tcs.SetResult(true);
// wait for task somewhere else
await tcs.Task;
When you have an unusual thing you need to await on, the easiest answer is often TaskCompletionSource (or some async-enabled primitive based on TaskCompletionSource).
In this case, your need is quite simple, so you can just use TaskCompletionSource directly:
private TaskCompletionSource<object> continueClicked;
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
// Note: You probably want to disable this button while "in progress" so the
// user can't click it twice.
await GetResults();
// And re-enable the button here, possibly in a finally block.
}
private async Task GetResults()
{
// Do lot of complex stuff that takes a long time
// (e.g. contact some web services)
// Wait for the user to click Continue.
continueClicked = new TaskCompletionSource<object>();
buttonContinue.Visibility = Visibility.Visible;
await continueClicked.Task;
buttonContinue.Visibility = Visibility.Collapsed;
// More work...
}
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
if (continueClicked != null)
continueClicked.TrySetResult(null);
}
Logically, TaskCompletionSource is like an async ManualResetEvent, except that you can only "set" the event once and the event can have a "result" (in this case, we're not using it, so we just set the result to null).
Here is a utility class that I use:
public class AsyncEventListener
{
private readonly Func<bool> _predicate;
public AsyncEventListener() : this(() => true)
{
}
public AsyncEventListener(Func<bool> predicate)
{
_predicate = predicate;
Successfully = new Task(() => { });
}
public void Listen(object sender, EventArgs eventArgs)
{
if (!Successfully.IsCompleted && _predicate.Invoke())
{
Successfully.RunSynchronously();
}
}
public Task Successfully { get; }
}
And here is how I use it:
var itChanged = new AsyncEventListener();
someObject.PropertyChanged += itChanged.Listen;
// ... make it change ...
await itChanged.Successfully;
someObject.PropertyChanged -= itChanged.Listen;
Ideally, you don't. While you certainly can block the async thread, that's a waste of resources, and not ideal.
Consider the canonical example where the user goes to lunch while the button is waiting to be clicked.
If you have halted your asynchronous code while waiting for the input from the user, then it's just wasting resources while that thread is paused.
That said, it's better if in your asynchronous operation, you set the state that you need to maintain to the point where the button is enabled and you're "waiting" on a click. At that point, your GetResults method stops.
Then, when the button is clicked, based on the state that you have stored, you start another asynchronous task to continue the work.
Because the SynchronizationContext will be captured in the event handler that calls GetResults (the compiler will do this as a result of using the await keyword being used, and the fact that SynchronizationContext.Current should be non-null, given you are in a UI application), you can use async/await like so:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
await GetResults();
// Show dialog/UI element. This code has been marshaled
// back to the UI thread because the SynchronizationContext
// was captured behind the scenes when
// await was called on the previous line.
...
// Check continue, if true, then continue with another async task.
if (_continue) await ContinueToGetResultsAsync();
}
private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
_continue = true;
}
private async Task GetResults()
{
// Do lot of complex stuff that takes a long time
// (e.g. contact some web services)
...
}
ContinueToGetResultsAsync is the method that continues to get the results in the event that your button is pushed. If your button is not pushed, then your event handler does nothing.
Simple Helper Class:
public class EventAwaiter<TEventArgs>
{
private readonly TaskCompletionSource<TEventArgs> _eventArrived = new TaskCompletionSource<TEventArgs>();
private readonly Action<EventHandler<TEventArgs>> _unsubscribe;
public EventAwaiter(Action<EventHandler<TEventArgs>> subscribe, Action<EventHandler<TEventArgs>> unsubscribe)
{
subscribe(Subscription);
_unsubscribe = unsubscribe;
}
public Task<TEventArgs> Task => _eventArrived.Task;
private EventHandler<TEventArgs> Subscription => (s, e) =>
{
_eventArrived.TrySetResult(e);
_unsubscribe(Subscription);
};
}
Usage:
var valueChangedEventAwaiter = new EventAwaiter<YourEventArgs>(
h => example.YourEvent += h,
h => example.YourEvent -= h);
await valueChangedEventAwaiter.Task;
Stephen Toub published this AsyncManualResetEvent class on his blog.
public class AsyncManualResetEvent
{
private volatile TaskCompletionSource<bool> m_tcs = new TaskCompletionSource<bool>();
public Task WaitAsync() { return m_tcs.Task; }
public void Set()
{
var tcs = m_tcs;
Task.Factory.StartNew(s => ((TaskCompletionSource<bool>)s).TrySetResult(true),
tcs, CancellationToken.None, TaskCreationOptions.PreferFairness, TaskScheduler.Default);
tcs.Task.Wait();
}
public void Reset()
{
while (true)
{
var tcs = m_tcs;
if (!tcs.Task.IsCompleted ||
Interlocked.CompareExchange(ref m_tcs, new TaskCompletionSource<bool>(), tcs) == tcs)
return;
}
}
}
With Reactive Extensions (Rx.Net)
var eventObservable = Observable
.FromEventPattern<EventArgs>(
h => example.YourEvent += h,
h => example.YourEvent -= h);
var res = await eventObservable.FirstAsync();
You can add Rx with Nuget Package System.Reactive
Tested Sample:
private static event EventHandler<EventArgs> _testEvent;
private static async Task Main()
{
var eventObservable = Observable
.FromEventPattern<EventArgs>(
h => _testEvent += h,
h => _testEvent -= h);
Task.Delay(5000).ContinueWith(_ => _testEvent?.Invoke(null, new EventArgs()));
var res = await eventObservable.FirstAsync();
Console.WriteLine("Event got fired");
}
I'm using my own AsyncEvent class for awaitable events.
public delegate Task AsyncEventHandler<T>(object sender, T args) where T : EventArgs;
public class AsyncEvent : AsyncEvent<EventArgs>
{
public AsyncEvent() : base()
{
}
}
public class AsyncEvent<T> where T : EventArgs
{
private readonly HashSet<AsyncEventHandler<T>> _handlers;
public AsyncEvent()
{
_handlers = new HashSet<AsyncEventHandler<T>>();
}
public void Add(AsyncEventHandler<T> handler)
{
_handlers.Add(handler);
}
public void Remove(AsyncEventHandler<T> handler)
{
_handlers.Remove(handler);
}
public async Task InvokeAsync(object sender, T args)
{
foreach (var handler in _handlers)
{
await handler(sender, args);
}
}
public static AsyncEvent<T> operator+(AsyncEvent<T> left, AsyncEventHandler<T> right)
{
var result = left ?? new AsyncEvent<T>();
result.Add(right);
return result;
}
public static AsyncEvent<T> operator-(AsyncEvent<T> left, AsyncEventHandler<T> right)
{
left.Remove(right);
return left;
}
}
To declare an event in the class that raises events:
public AsyncEvent MyNormalEvent;
public AsyncEvent<ProgressEventArgs> MyCustomEvent;
To raise the events:
if (MyNormalEvent != null) await MyNormalEvent.InvokeAsync(this, new EventArgs());
if (MyCustomEvent != null) await MyCustomEvent.InvokeAsync(this, new ProgressEventArgs());
To subscribe to the events:
MyControl.Click += async (sender, args) => {
// await...
}
MyControl.Click += (sender, args) => {
// synchronous code
return Task.CompletedTask;
}
Here is a small toolbox of six methods, that can be used for converting events to tasks:
/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on <see cref="EventHandler"/>, to a Task.</summary>
public static Task EventToAsync(
Action<EventHandler> addHandler,
Action<EventHandler> removeHandler)
{
var tcs = new TaskCompletionSource<object>();
addHandler(Handler);
return tcs.Task;
void Handler(object sender, EventArgs e)
{
removeHandler(Handler);
tcs.SetResult(null);
}
}
/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on <see cref="EventHandler{TEventArgs}"/>, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TEventArgs>(
Action<EventHandler<TEventArgs>> addHandler,
Action<EventHandler<TEventArgs>> removeHandler)
{
var tcs = new TaskCompletionSource<TEventArgs>();
addHandler(Handler);
return tcs.Task;
void Handler(object sender, TEventArgs e)
{
removeHandler(Handler);
tcs.SetResult(e);
}
}
/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on a supplied event delegate type, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TDelegate, TEventArgs>(
Action<TDelegate> addHandler, Action<TDelegate> removeHandler)
{
var tcs = new TaskCompletionSource<TEventArgs>();
TDelegate handler = default;
Action<object, TEventArgs> genericHandler = (sender, e) =>
{
removeHandler(handler);
tcs.SetResult(e);
};
handler = (TDelegate)(object)genericHandler.GetType().GetMethod("Invoke")
.CreateDelegate(typeof(TDelegate), genericHandler);
addHandler(handler);
return tcs.Task;
}
/// <summary>Converts a named .NET event, conforming to the standard .NET event
/// pattern based on <see cref="EventHandler"/>, to a Task.</summary>
public static Task EventToAsync(object target, string eventName)
{
var type = target.GetType();
var eventInfo = type.GetEvent(eventName);
if (eventInfo == null) throw new InvalidOperationException("Event not found.");
var tcs = new TaskCompletionSource<object>();
EventHandler handler = default;
handler = new EventHandler((sender, e) =>
{
eventInfo.RemoveEventHandler(target, handler);
tcs.SetResult(null);
});
eventInfo.AddEventHandler(target, handler);
return tcs.Task;
}
/// <summary>Converts a named .NET event, conforming to the standard .NET event
/// pattern based on <see cref="EventHandler{TEventArgs}"/>, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TEventArgs>(
object target, string eventName)
{
var type = target.GetType();
var eventInfo = type.GetEvent(eventName);
if (eventInfo == null) throw new InvalidOperationException("Event not found.");
var tcs = new TaskCompletionSource<TEventArgs>();
EventHandler<TEventArgs> handler = default;
handler = new EventHandler<TEventArgs>((sender, e) =>
{
eventInfo.RemoveEventHandler(target, handler);
tcs.SetResult(e);
});
eventInfo.AddEventHandler(target, handler);
return tcs.Task;
}
/// <summary>Converts a generic Action-based .NET event to a Task.</summary>
public static Task<TArgument> EventActionToAsync<TArgument>(
Action<Action<TArgument>> addHandler,
Action<Action<TArgument>> removeHandler)
{
var tcs = new TaskCompletionSource<TArgument>();
addHandler(Handler);
return tcs.Task;
void Handler(TArgument arg)
{
removeHandler(Handler);
tcs.SetResult(arg);
}
}
All these methods are creating a Task that will complete with the next invocation of the associated event. This task can never become faulted or canceled, it may only complete successfully.
Usage example with a standard event (Progress<T>.ProgressChanged):
var p = new Progress<int>();
//...
int result = await EventToAsync<int>(
h => p.ProgressChanged += h, h => p.ProgressChanged -= h);
// ...or...
int result = await EventToAsync<EventHandler<int>, int>(
h => p.ProgressChanged += h, h => p.ProgressChanged -= h);
// ...or...
int result = await EventToAsync<int>(p, "ProgressChanged");
Usage example with a non-standard event:
public static event Action<int> MyEvent;
//...
int result = await EventActionToAsync<int>(h => MyEvent += h, h => MyEvent -= h);
The event is unsubscribed when the task is completed. No mechanism is provided for unsubscribing earlier than that.
Here is a class I used for testing, which support CancellationToken.
This Test method shows us awaiting an instance of ClassWithEvent's MyEvent to be raised. :
public async Task TestEventAwaiter()
{
var cls = new ClassWithEvent();
Task<bool> isRaisedTask = EventAwaiter<ClassWithEvent>.RunAsync(
cls,
nameof(ClassWithEvent.MyMethodEvent),
TimeSpan.FromSeconds(3));
cls.Raise();
Assert.IsTrue(await isRaisedTask);
isRaisedTask = EventAwaiter<ClassWithEvent>.RunAsync(
cls,
nameof(ClassWithEvent.MyMethodEvent),
TimeSpan.FromSeconds(1));
System.Threading.Thread.Sleep(2000);
Assert.IsFalse(await isRaisedTask);
}
Here's the event awaiter class.
public class EventAwaiter<TOwner>
{
private readonly TOwner_owner;
private readonly string _eventName;
private readonly TaskCompletionSource<bool> _taskCompletionSource;
private readonly CancellationTokenSource _elapsedCancellationTokenSource;
private readonly CancellationTokenSource _linkedCancellationTokenSource;
private readonly CancellationToken _activeCancellationToken;
private Delegate _localHookDelegate;
private EventInfo _eventInfo;
public static Task<bool> RunAsync(
TOwner owner,
string eventName,
TimeSpan timeout,
CancellationToken? cancellationToken = null)
{
return (new EventAwaiter<TOwner>(owner, eventName, timeout, cancellationToken)).RunAsync(timeout);
}
private EventAwaiter(
TOwner owner,
string eventName,
TimeSpan timeout,
CancellationToken? cancellationToken = null)
{
if (owner == null) throw new TypeInitializationException(this.GetType().FullName, new ArgumentNullException(nameof(owner)));
if (eventName == null) throw new TypeInitializationException(this.GetType().FullName, new ArgumentNullException(nameof(eventName)));
_owner = owner;
_eventName = eventName;
_taskCompletionSource = new TaskCompletionSource<bool>();
_elapsedCancellationTokenSource = new CancellationTokenSource();
_linkedCancellationTokenSource =
cancellationToken == null
? null
: CancellationTokenSource.CreateLinkedTokenSource(_elapsedCancellationTokenSource.Token, cancellationToken.Value);
_activeCancellationToken = (_linkedCancellationTokenSource ?? _elapsedCancellationTokenSource).Token;
_eventInfo = typeof(TOwner).GetEvent(_eventName);
Type eventHandlerType = _eventInfo.EventHandlerType;
MethodInfo invokeMethodInfo = eventHandlerType.GetMethod("Invoke");
var parameterTypes = Enumerable.Repeat(this.GetType(),1).Concat(invokeMethodInfo.GetParameters().Select(p => p.ParameterType)).ToArray();
DynamicMethod eventRedirectorMethod = new DynamicMethod("EventRedirect", typeof(void), parameterTypes);
ILGenerator generator = eventRedirectorMethod.GetILGenerator();
generator.Emit(OpCodes.Nop);
generator.Emit(OpCodes.Ldarg_0);
generator.EmitCall(OpCodes.Call, this.GetType().GetMethod(nameof(OnEventRaised),BindingFlags.Public | BindingFlags.Instance), null);
generator.Emit(OpCodes.Ret);
_localHookDelegate = eventRedirectorMethod.CreateDelegate(eventHandlerType,this);
}
private void AddHandler()
{
_eventInfo.AddEventHandler(_owner, _localHookDelegate);
}
private void RemoveHandler()
{
_eventInfo.RemoveEventHandler(_owner, _localHookDelegate);
}
private Task<bool> RunAsync(TimeSpan timeout)
{
AddHandler();
Task.Delay(timeout, _activeCancellationToken).
ContinueWith(TimeOutTaskCompleted);
return _taskCompletionSource.Task;
}
private void TimeOutTaskCompleted(Task tsk)
{
RemoveHandler();
if (_elapsedCancellationTokenSource.IsCancellationRequested) return;
if (_linkedCancellationTokenSource?.IsCancellationRequested == true)
SetResult(TaskResult.Cancelled);
else if (!_taskCompletionSource.Task.IsCompleted)
SetResult(TaskResult.Failed);
}
public void OnEventRaised()
{
RemoveHandler();
if (_taskCompletionSource.Task.IsCompleted)
{
if (!_elapsedCancellationTokenSource.IsCancellationRequested)
_elapsedCancellationTokenSource?.Cancel(false);
}
else
{
if (!_elapsedCancellationTokenSource.IsCancellationRequested)
_elapsedCancellationTokenSource?.Cancel(false);
SetResult(TaskResult.Success);
}
}
enum TaskResult { Failed, Success, Cancelled }
private void SetResult(TaskResult result)
{
if (result == TaskResult.Success)
_taskCompletionSource.SetResult(true);
else if (result == TaskResult.Failed)
_taskCompletionSource.SetResult(false);
else if (result == TaskResult.Cancelled)
_taskCompletionSource.SetCanceled();
Dispose();
}
public void Dispose()
{
RemoveHandler();
_elapsedCancellationTokenSource?.Dispose();
_linkedCancellationTokenSource?.Dispose();
}
}
It basically relies on CancellationTokenSource to report back the result.
It uses some IL injection to create a delegate to match the event's signature.
That delegate is then added as a handler for that event using some reflection.
The body of the generate method simply calls another function on the EventAwaiter class, which then reports success using the CancellationTokenSource.
Caution, do not use this, as is, in product. This is meant as a working example.
For instance, IL generation is an expensive process. You should avoid regenerate the same method over and over again, and instead cache these.
AsyncEx has AsyncManualResetEvent for this. You can:
var signal = new AsyncManualResetEvent();
await signal.WaitAsync();
And trigger it with:
signal.Set();
I am creating a class that has a series of events, one of them being GameShuttingDown. When this event is fired, I need to invoke the event handler. The point of this event is to notify users the game is shutting down and they need to save their data. The saves are awaitable, and events are not. So when the handler gets called, the game shuts down before the awaited handlers can complete.
public event EventHandler<EventArgs> GameShuttingDown;
public virtual async Task ShutdownGame()
{
await this.NotifyGameShuttingDown();
await this.SaveWorlds();
this.NotifyGameShutDown();
}
private async Task SaveWorlds()
{
foreach (DefaultWorld world in this.Worlds)
{
await this.worldService.SaveWorld(world);
}
}
protected virtual void NotifyGameShuttingDown()
{
var handler = this.GameShuttingDown;
if (handler == null)
{
return;
}
handler(this, new EventArgs());
}
Event registration
// The game gets shut down before this completes because of the nature of how events work
DefaultGame.GameShuttingDown += async (sender, args) => await this.repo.Save(blah);
I understand that the signature for events are void EventName and so making it async is basically fire and forget. My engine makes heavy use of eventing to notify 3rd party developers (and multiple internal components) that events are taking place within the engine and letting them react to them.
Is there a good route to go down to replace eventing with something asynchronous based that I can use? I'm not sure if I should be using BeginShutdownGame and EndShutdownGame with callbacks, but that's a pain because then only the calling source can pass a callback, and not any 3rd party stuff that plugs in to the engine, which is what I am getting with events. If the server calls game.ShutdownGame(), there's no way for engine plugins and or other components within the engine to pass along their callbacks, unless I wire up some kind of registration method, keeping a collection of callbacks.
Any advice on what the preferred/recommended route to go down with this would be greatly appreciated! I have looked around and for the most part what I've seen is using the Begin/End approach which I don't think will satisfy what I'm wanting to do.
Edit
Another option I'm considering is using a registration method, that takes an awaitable callback. I iterate over all of the callbacks, grab their Task and await with a WhenAll.
private List<Func<Task>> ShutdownCallbacks = new List<Func<Task>>();
public void RegisterShutdownCallback(Func<Task> callback)
{
this.ShutdownCallbacks.Add(callback);
}
public async Task Shutdown()
{
var callbackTasks = new List<Task>();
foreach(var callback in this.ShutdownCallbacks)
{
callbackTasks.Add(callback());
}
await Task.WhenAll(callbackTasks);
}
Personally, I think that having async event handlers may not be the best design choice, not the least of which reason being the very problem you're having. With synchronous handlers, it's trivial to know when they complete.
That said, if for some reason you must or at least are strongly compelled to stick with this design, you can do it in an await-friendly way.
Your idea to register handlers and await them is a good one. However, I would suggest sticking with the existing event paradigm, as that will keep the expressiveness of events in your code. The main thing is that you have to deviate from the standard EventHandler-based delegate type, and use a delegate type that returns a Task so that you can await the handlers.
Here's a simple example illustrating what I mean:
class A
{
public event Func<object, EventArgs, Task> Shutdown;
public async Task OnShutdown()
{
Func<object, EventArgs, Task> handler = Shutdown;
if (handler == null)
{
return;
}
Delegate[] invocationList = handler.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, EventArgs, Task>)invocationList[i])(this, EventArgs.Empty);
}
await Task.WhenAll(handlerTasks);
}
}
The OnShutdown() method, after doing the standard "get local copy of the event delegate instance", first invokes all of the handlers, and then awaits all of the returned Tasks (having saved them to a local array as the handlers are invoked).
Here's a short console program illustrating the use:
class Program
{
static void Main(string[] args)
{
A a = new A();
a.Shutdown += Handler1;
a.Shutdown += Handler2;
a.Shutdown += Handler3;
a.OnShutdown().Wait();
}
static async Task Handler1(object sender, EventArgs e)
{
Console.WriteLine("Starting shutdown handler #1");
await Task.Delay(1000);
Console.WriteLine("Done with shutdown handler #1");
}
static async Task Handler2(object sender, EventArgs e)
{
Console.WriteLine("Starting shutdown handler #2");
await Task.Delay(5000);
Console.WriteLine("Done with shutdown handler #2");
}
static async Task Handler3(object sender, EventArgs e)
{
Console.WriteLine("Starting shutdown handler #3");
await Task.Delay(2000);
Console.WriteLine("Done with shutdown handler #3");
}
}
Having gone through this example, I now find myself wondering if there couldn't have been a way for C# to abstract this a bit. Maybe it would have been too complicated a change, but the current mix of the old-style void-returning event handlers and the new async/await feature does seem a bit awkward. The above works (and works well, IMHO), but it would have been nice to have better CLR and/or language support for the scenario (i.e. be able to await a multicast delegate and have the C# compiler turn that into a call to WhenAll()).
Peter's example is great, I've just simplified it a little using LINQ and extensions:
public static class AsynchronousEventExtensions
{
public static Task Raise<TSource, TEventArgs>(this Func<TSource, TEventArgs, Task> handlers, TSource source, TEventArgs args)
where TEventArgs : EventArgs
{
if (handlers != null)
{
return Task.WhenAll(handlers.GetInvocationList()
.OfType<Func<TSource, TEventArgs, Task>>()
.Select(h => h(source, args)));
}
return Task.CompletedTask;
}
}
It may be a good idea to add a timeout. To raise the event call Raise extension:
public event Func<A, EventArgs, Task> Shutdown;
private async Task SomeMethod()
{
...
await Shutdown.Raise(this, EventArgs.Empty);
...
}
But you have to be aware that, unlike synchronous evens, this implementation calls handlers concurrently. It can be an issue if handlers have to be executed strictly consecutively what they are often do, e.g. a next handler depends on results of the previous one:
someInstance.Shutdown += OnShutdown1;
someInstance.Shutdown += OnShutdown2;
...
private async Task OnShutdown1(SomeClass source, MyEventArgs args)
{
if (!args.IsProcessed)
{
// An operation
await Task.Delay(123);
args.IsProcessed = true;
}
}
private async Task OnShutdown2(SomeClass source, MyEventArgs args)
{
// OnShutdown2 will start execution the moment OnShutdown1 hits await
// and will proceed to the operation, which is not the desired behavior.
// Or it can be just a concurrent DB query using the same connection
// which can result in an exception thrown base on the provider
// and connection string options
if (!args.IsProcessed)
{
// An operation
await Task.Delay(123);
args.IsProcessed = true;
}
}
You'd better change the extension method to call handlers consecutively:
public static class AsynchronousEventExtensions
{
public static async Task Raise<TSource, TEventArgs>(this Func<TSource, TEventArgs, Task> handlers, TSource source, TEventArgs args)
where TEventArgs : EventArgs
{
if (handlers != null)
{
foreach (Func<TSource, TEventArgs, Task> handler in handlers.GetInvocationList())
{
await handler(source, args);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Example
{
// delegate as alternative standard EventHandler
public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e, CancellationToken token);
public class ExampleObject
{
// use as regular event field
public event AsyncEventHandler<EventArgs> AsyncEvent;
// invoke using the extension method
public async Task InvokeEventAsync(CancellationToken token) {
await this.AsyncEvent.InvokeAsync(this, EventArgs.Empty, token);
}
// subscribe (add a listener) with regular syntax
public static async Task UsageAsync() {
var item = new ExampleObject();
item.AsyncEvent += (sender, e, token) => Task.CompletedTask;
await item.InvokeEventAsync(CancellationToken.None);
}
}
public static class AsynEventHandlerExtensions
{
// invoke a async event (with null-checking)
public static async Task InvokeAsync<TEventArgs>(this AsyncEventHandler<TEventArgs> handler, object sender, TEventArgs args, CancellationToken token) {
var delegates = handler?.GetInvocationList();
if (delegates?.Length > 0) {
var tasks = delegates
.Cast<AsyncEventHandler<TEventArgs>>()
.Select(e => e.Invoke(sender, args, token));
await Task.WhenAll(tasks);
}
}
}
}
internal static class EventExtensions
{
public static void InvokeAsync<TEventArgs>(this EventHandler<TEventArgs> #event, object sender,
TEventArgs args, AsyncCallback ar, object userObject = null)
where TEventArgs : class
{
var listeners = #event.GetInvocationList();
foreach (var t in listeners)
{
var handler = (EventHandler<TEventArgs>) t;
handler.BeginInvoke(sender, args, ar, userObject);
}
}
}
example:
public event EventHandler<CodeGenEventArgs> CodeGenClick;
private void CodeGenClickAsync(CodeGenEventArgs args)
{
CodeGenClick.InvokeAsync(this, args, ar =>
{
InvokeUI(() =>
{
if (args.Code.IsNotNullOrEmpty())
{
var oldValue = (string) gv.GetRowCellValue(gv.FocusedRowHandle, nameof(License.Code));
if (oldValue != args.Code)
gv.SetRowCellValue(gv.FocusedRowHandle, nameof(License.Code), args.Code);
}
});
});
}
Note: This is async so the event handler may compromise the UI thread. The event handler (subscriber) should do no UI-work.
It wouldn't make much sense otherwise.
declare your event in your event provider:
public event EventHandler DoSomething;
Invoke event your provider:
DoSomething.InvokeAsync(new MyEventArgs(), this, ar => { callback called when finished (synchronize UI when needed here!) }, null);
subscribe the event by client as you would normally do
It's true, events are inherently un-awaitable so you'll have to work around it.
One solution I have used in the past is using a semaphore to wait for all entries in it to be released. In my situation I only had one subscribed event so I could hardcode it as new SemaphoreSlim(0, 1) but in your case you might want to override the getter/setter for your event and keep a counter of how many subscribers there are so you can dynamically set the max amount of simultaneous threads.
Afterwards you pass a semaphore entry to each of the subscribers and let them do their thing until SemaphoreSlim.CurrentCount == amountOfSubscribers (aka: all spots have been freed).
This would essentially block your program until all event subscribers have finished.
You might also want to consider providing an event à la GameShutDownFinished for your subscribers, which they have to call when they're done with their end-of-game task. Combined with the SemaphoreSlim.Release(int) overload you can now clear up all semaphore entries and simply use Semaphore.Wait() to block the thread. Instead of having to check whether or not all entries have been cleared you now wait until one spot has been freed (but there should only one moment where all spots are freed at once).
I know that the op was asking specifically about using async and tasks for this, but here is an alternative that means the handlers do not need to return a value. The code is based on Peter Duniho's example. First the equivalent class A (squashed up a bit to fit) :-
class A
{
public delegate void ShutdownEventHandler(EventArgs e);
public event ShutdownEventHandler ShutdownEvent;
public void OnShutdownEvent(EventArgs e)
{
ShutdownEventHandler handler = ShutdownEvent;
if (handler == null) { return; }
Delegate[] invocationList = handler.GetInvocationList();
Parallel.ForEach<Delegate>(invocationList,
(hndler) => { ((ShutdownEventHandler)hndler)(e); });
}
}
A simple console application to show its use...
using System;
using System.Threading;
using System.Threading.Tasks;
...
class Program
{
static void Main(string[] args)
{
A a = new A();
a.ShutdownEvent += Handler1;
a.ShutdownEvent += Handler2;
a.ShutdownEvent += Handler3;
a.OnShutdownEvent(new EventArgs());
Console.WriteLine("Handlers should all be done now.");
Console.ReadKey();
}
static void handlerCore( int id, int offset, int num )
{
Console.WriteLine("Starting shutdown handler #{0}", id);
int step = 200;
Thread.Sleep(offset);
for( int i = 0; i < num; i += step)
{
Thread.Sleep(step);
Console.WriteLine("...Handler #{0} working - {1}/{2}", id, i, num);
}
Console.WriteLine("Done with shutdown handler #{0}", id);
}
static void Handler1(EventArgs e) { handlerCore(1, 7, 5000); }
static void Handler2(EventArgs e) { handlerCore(2, 5, 3000); }
static void Handler3(EventArgs e) { handlerCore(3, 3, 1000); }
}
I hope that this is useful to someone.
If you need to await a standard .net event handler you can't do that, because it's void.
But you can create an async event system to handle that:
public delegate Task AsyncEventHandler(AsyncEventArgs e);
public class AsyncEventArgs : System.EventArgs
{
public bool Handled { get; set; }
}
public class AsyncEvent
{
private string name;
private List<AsyncEventHandler> handlers;
private Action<string, Exception> errorHandler;
public AsyncEvent(string name, Action<string, Exception> errorHandler)
{
this.name = name;
this.handlers = new List<AsyncEventHandler>();
this.errorHandler = errorHandler;
}
public void Register(AsyncEventHandler handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this.handlers)
this.handlers.Add(handler);
}
public void Unregister(AsyncEventHandler handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this.handlers)
this.handlers.Remove(handler);
}
public IReadOnlyList<AsyncEventHandler> Handlers
{
get
{
var temp = default(AsyncEventHandler[]);
lock (this.handlers)
temp = this.handlers.ToArray();
return temp.ToList().AsReadOnly();
}
}
public async Task InvokeAsync()
{
var ev = new AsyncEventArgs();
var exceptions = new List<Exception>();
foreach (var handler in this.Handlers)
{
try
{
await handler(ev).ConfigureAwait(false);
if (ev.Handled)
break;
}
catch(Exception ex)
{
exceptions.Add(ex);
}
}
if (exceptions.Any())
this.errorHandler?.Invoke(this.name, new AggregateException(exceptions));
}
}
And you can declare now your async events:
public class MyGame
{
private AsyncEvent _gameShuttingDown;
public event AsyncEventHandler GameShuttingDown
{
add => this._gameShuttingDown.Register(value);
remove => this._gameShuttingDown.Unregister(value);
}
void ErrorHandler(string name, Exception ex)
{
// handle event error.
}
public MyGame()
{
this._gameShuttingDown = new AsyncEvent("GAME_SHUTTING_DOWN", this.ErrorHandler);.
}
}
And invoke your async event using:
internal async Task NotifyGameShuttingDownAsync()
{
await this._gameShuttingDown.InvokeAsync().ConfigureAwait(false);
}
Generic version:
public delegate Task AsyncEventHandler<in T>(T e) where T : AsyncEventArgs;
public class AsyncEvent<T> where T : AsyncEventArgs
{
private string name;
private List<AsyncEventHandler<T>> handlers;
private Action<string, Exception> errorHandler;
public AsyncEvent(string name, Action<string, Exception> errorHandler)
{
this.name = name;
this.handlers = new List<AsyncEventHandler<T>>();
this.errorHandler = errorHandler;
}
public void Register(AsyncEventHandler<T> handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this.handlers)
this.handlers.Add(handler);
}
public void Unregister(AsyncEventHandler<T> handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this.handlers)
this.handlers.Remove(handler);
}
public IReadOnlyList<AsyncEventHandler<T>> Handlers
{
get
{
var temp = default(AsyncEventHandler<T>[]);
lock (this.handlers)
temp = this.handlers.ToArray();
return temp.ToList().AsReadOnly();
}
}
public async Task InvokeAsync(T ev)
{
var exceptions = new List<Exception>();
foreach (var handler in this.Handlers)
{
try
{
await handler(ev).ConfigureAwait(false);
if (ev.Handled)
break;
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
if (exceptions.Any())
this.errorHandler?.Invoke(this.name, new AggregateException(exceptions));
}
}
I'm having a problem with using multiple UI dispatchers to modify an list that is bound to the UI. The method with just exit when it hits the first dispatcher. If I wrap the entire method in the dispatcher it works, but I have another solution but I'm not sure it's appropriate:
Basically, I have a socket listening in a never-ending loop for network commands from a media device. When it finds one, it calls ProcessCommand.
That function calls one of 50+ methods to process the specific commands. Those functions store the internal state, but mainly raise events my main application can subscribe to so it knows when something like the volume has changed and I can update the UI.
These work pretty well, except the one case noted at the start where I need to modify a state object that is bound using several dispatchers in the same method and it not working, and wrapping the method in one big dispatcher seems to work.
Another solution I found is to run the ProcessCommand running in the socket background listener on the UI dispatcher, ie:
CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
ProcessCommand(command);
});
This will then trickle down into all of the individual 50+ ProcessCommandXYZ methods, and also in my main app where I subscribe to these events on multiple pages I currently have to use a UI dispatcher on every one, so something like:
Socket listener background task > {DispatcherUI ProcessCommand} > ProcessVolumeCommand > Raise OnVolumeChangedEvent > Subscriber updates UI
Would make it so I don't need to put each event subscriber into a dispatcher, but most importantly fixes problem I have with multiple dispatchers in one method:
Using Dispatcher correctly in event to update UI
Does this seem like a good solution?
Here is my solution for such situations when you subscribed to event that raises on Non-ui thread.
//subscribe to event
//handler (OnPlayerVolumeChanged) will be invoked on subscriber (UI) thread
var subscriptionToken = EventSubscription.FromEvent<VolumeChangedEventArgs>(
handler => _player.VolumeChanged += handler,
handler => _player.VolumeChanged -= handler,
OnPlayerVolumeChanged,
HandlerThreadOption.SubscriberThread);
//unsubscribe
subscriptionToken.Unsubscribe();
And here is implementation:
public enum HandlerThreadOption
{
PublisherThread,
SubscriberThread,
BackgroundThread
}
public class EventSubscriptionToken : IDisposable
{
private readonly Action _unsubscribe;
public EventSubscriptionToken(Action unsubscribe)
{
_unsubscribe = unsubscribe;
}
public void Unsubscribe()
{
_unsubscribe();
}
void IDisposable.Dispose()
{
Unsubscribe();
}
}
public static class EventSubscription
{
public static EventSubscriptionToken FromEvent<TEventArgs>(
Action<EventHandler<TEventArgs>> addHandler,
Action<EventHandler<TEventArgs>> removeHandler,
EventHandler<TEventArgs> handler,
HandlerThreadOption threadOption)
{
var threadSpecificHandler = GetHandler(handler, threadOption);
addHandler(threadSpecificHandler);
return new EventSubscriptionToken(() => removeHandler(threadSpecificHandler));
}
public static EventSubscriptionToken FromEvent(
Action<EventHandler> addHandler,
Action<EventHandler> removeHandler,
EventHandler handler,
HandlerThreadOption threadOption)
{
var threadSpecificHandler = GetHandler(handler, threadOption);
addHandler(threadSpecificHandler);
return new EventSubscriptionToken(() => removeHandler(threadSpecificHandler));
}
private static EventHandler<T> GetHandler<T>(EventHandler<T> handler, HandlerThreadOption threadOption)
{
switch (threadOption)
{
case HandlerThreadOption.PublisherThread:
return handler;
case HandlerThreadOption.SubscriberThread:
return GetCurrentThreadExecutionStrategy(handler);
case HandlerThreadOption.BackgroundThread:
return GetBackgroundThreadExecutionStrategy(handler);
default:
throw new ArgumentOutOfRangeException("threadOption");
}
}
private static EventHandler GetHandler(EventHandler handler, HandlerThreadOption threadOption)
{
switch (threadOption)
{
case HandlerThreadOption.PublisherThread:
return handler;
case HandlerThreadOption.SubscriberThread:
return GetCurrentThreadExecutionStrategy(handler);
case HandlerThreadOption.BackgroundThread:
return GetBackgroundThreadExecutionStrategy(handler);
default:
throw new ArgumentOutOfRangeException("threadOption");
}
}
private static EventHandler<T> GetBackgroundThreadExecutionStrategy<T>(EventHandler<T> action)
{
return (sender, e) => Task.Factory.StartNew(() => action(sender, e));
}
private static EventHandler GetBackgroundThreadExecutionStrategy(EventHandler handler)
{
return (sender, e) => Task.Factory.StartNew(() => handler(sender, e));
}
private static EventHandler<T> GetCurrentThreadExecutionStrategy<T>(EventHandler<T> action)
{
var currentSynchronizationContext = SynchronizationContext.Current;
return (sender, e) => PostToSynchronizationContext(currentSynchronizationContext, () => action(sender, e));
}
private static EventHandler GetCurrentThreadExecutionStrategy(EventHandler handler)
{
var currentSynchronizationContext = SynchronizationContext.Current;
return (sender, e) => PostToSynchronizationContext(currentSynchronizationContext, () => handler(sender, e));
}
private static void PostToSynchronizationContext(SynchronizationContext synchronizationContext, Action action)
{
try
{
synchronizationContext.Post(state => action(), null);
}
catch (Exception ex)
{
if (!ex.Message.StartsWith("The operation cannot be completed because the window is being closed", StringComparison.Ordinal))
{
throw;
}
}
}
}
I'd like to have a generic reusable piece of code for wrapping EAP pattern as task, something similar to what Task.Factory.FromAsync does for BeginXXX/EndXXX APM pattern.
E.g.:
private async void Form1_Load(object sender, EventArgs e)
{
await TaskExt.FromEvent<EventArgs>(
handler => this.webBrowser.DocumentCompleted +=
new WebBrowserDocumentCompletedEventHandler(handler),
() => this.webBrowser.Navigate("about:blank"),
handler => this.webBrowser.DocumentCompleted -=
new WebBrowserDocumentCompletedEventHandler(handler),
CancellationToken.None);
this.webBrowser.Document.InvokeScript("setTimeout",
new[] { "document.body.style.backgroundColor = 'yellow'", "1" });
}
So far, it looks like this:
public static class TaskExt
{
public static async Task<TEventArgs> FromEvent<TEventArgs>(
Action<EventHandler<TEventArgs>> registerEvent,
Action action,
Action<EventHandler<TEventArgs>> unregisterEvent,
CancellationToken token)
{
var tcs = new TaskCompletionSource<TEventArgs>();
EventHandler<TEventArgs> handler = (sender, args) =>
tcs.TrySetResult(args);
registerEvent(handler);
try
{
using (token.Register(() => tcs.SetCanceled()))
{
action();
return await tcs.Task;
}
}
finally
{
unregisterEvent(handler);
}
}
}
Is it possible to come up with something similar, which nevertheless would not require me to type WebBrowserDocumentCompletedEventHandler twice (for registerEvent/unregisterEvent), without resorting to reflection?
It is possible with a helper class and a fluent-like syntax:
public static class TaskExt
{
public static EAPTask<TEventArgs, EventHandler<TEventArgs>> FromEvent<TEventArgs>()
{
var tcs = new TaskCompletionSource<TEventArgs>();
var handler = new EventHandler<TEventArgs>((s, e) => tcs.TrySetResult(e));
return new EAPTask<TEventArgs, EventHandler<TEventArgs>>(tcs, handler);
}
}
public sealed class EAPTask<TEventArgs, TEventHandler>
where TEventHandler : class
{
private readonly TaskCompletionSource<TEventArgs> _completionSource;
private readonly TEventHandler _eventHandler;
public EAPTask(
TaskCompletionSource<TEventArgs> completionSource,
TEventHandler eventHandler)
{
_completionSource = completionSource;
_eventHandler = eventHandler;
}
public EAPTask<TEventArgs, TOtherEventHandler> WithHandlerConversion<TOtherEventHandler>(
Converter<TEventHandler, TOtherEventHandler> converter)
where TOtherEventHandler : class
{
return new EAPTask<TEventArgs, TOtherEventHandler>(
_completionSource, converter(_eventHandler));
}
public async Task<TEventArgs> Start(
Action<TEventHandler> subscribe,
Action action,
Action<TEventHandler> unsubscribe,
CancellationToken cancellationToken)
{
subscribe(_eventHandler);
try
{
using(cancellationToken.Register(() => _completionSource.SetCanceled()))
{
action();
return await _completionSource.Task;
}
}
finally
{
unsubscribe(_eventHandler);
}
}
}
Now you have a WithHandlerConversion helper method, which can infer type parameter from converter argument, which means you need to write WebBrowserDocumentCompletedEventHandler only one time.
Usage:
await TaskExt
.FromEvent<WebBrowserDocumentCompletedEventArgs>()
.WithHandlerConversion(handler => new WebBrowserDocumentCompletedEventHandler(handler))
.Start(
handler => this.webBrowser.DocumentCompleted += handler,
() => this.webBrowser.Navigate(#"about:blank"),
handler => this.webBrowser.DocumentCompleted -= handler,
CancellationToken.None);
I have a (usage wise) much shorter Solution. I will show you the usage first and then give you the code that makes this happen (use it freely).
usage eg:
await button.EventAsync(nameof(button.Click));
or:
var specialEventArgs = await busniessObject.EventAsync(nameof(busniessObject.CustomerCreated));
or for Events that need to be triggered in some way:
var serviceResult = await service.EventAsync(()=> service.Start, nameof(service.Completed));
the magic that makes this happen (beware it's C# 7.1 syntax but can easily be converted back to lower language versions by adding a few lines):
using System;
using System.Threading;
using System.Threading.Tasks;
namespace SpacemonsterIndustries.Core
{
public static class EventExtensions
{
/// <summary>
/// Extension Method that converts a typical EventArgs Event into an awaitable Task
/// </summary>
/// <typeparam name="TEventArgs">The type of the EventArgs (must inherit from EventArgs)</typeparam>
/// <param name="objectWithEvent">the object that has the event</param>
/// <param name="trigger">optional Function that triggers the event</param>
/// <param name="eventName">the name of the event -> use nameof to be safe, e.g. nameof(button.Click) </param>
/// <param name="ct">an optional Cancellation Token</param>
/// <returns></returns>
public static async Task<TEventArgs> EventAsync<TEventArgs>(this object objectWithEvent, Action trigger, string eventName, CancellationToken ct = default)
where TEventArgs : EventArgs
{
var completionSource = new TaskCompletionSource<TEventArgs>(ct);
var eventInfo = objectWithEvent.GetType().GetEvent(eventName);
var delegateDef = new UniversalEventDelegate<TEventArgs>(Handler);
var handlerAsDelegate = Delegate.CreateDelegate(eventInfo.EventHandlerType, delegateDef.Target, delegateDef.Method);
eventInfo.AddEventHandler(objectWithEvent, handlerAsDelegate);
trigger?.Invoke();
var result = await completionSource.Task;
eventInfo.RemoveEventHandler(objectWithEvent, handlerAsDelegate);
return result;
void Handler(object sender, TEventArgs e) => completionSource.SetResult(e);
}
public static Task<TEventArgs> EventAsync<TEventArgs>(this object objectWithEvent, string eventName, CancellationToken ct = default) where TEventArgs : EventArgs
=> EventAsync<TEventArgs>(objectWithEvent, null, eventName, ct);
private delegate void UniversalEventDelegate<in TEventArgs>(object sender, TEventArgs e) where TEventArgs : EventArgs;
}
}
I think the following version might be satisfactory enough. I did borrow the idea of preparing a correctly typed event handler from max's answer, but this implementation doesn't create any additional object explicitly.
As a positive side effect, it allows the caller to cancel or reject the result of the operation (with an exception), based upon the event's arguments (like AsyncCompletedEventArgs.Cancelled, AsyncCompletedEventArgs.Error).
The underlying TaskCompletionSource is still completely hidden from the caller (so it could be replaced with something else, e.g. a custom awaiter or a custom promise):
private async void Form1_Load(object sender, EventArgs e)
{
await TaskExt.FromEvent<WebBrowserDocumentCompletedEventHandler, EventArgs>(
getHandler: (completeAction, cancelAction, rejectAction) =>
(eventSource, eventArgs) => completeAction(eventArgs),
subscribe: eventHandler =>
this.webBrowser.DocumentCompleted += eventHandler,
unsubscribe: eventHandler =>
this.webBrowser.DocumentCompleted -= eventHandler,
initiate: (completeAction, cancelAction, rejectAction) =>
this.webBrowser.Navigate("about:blank"),
token: CancellationToken.None);
this.webBrowser.Document.InvokeScript("setTimeout",
new[] { "document.body.style.backgroundColor = 'yellow'", "1" });
}
public static class TaskExt
{
public static async Task<TEventArgs> FromEvent<TEventHandler, TEventArgs>(
Func<Action<TEventArgs>, Action, Action<Exception>, TEventHandler> getHandler,
Action<TEventHandler> subscribe,
Action<TEventHandler> unsubscribe,
Action<Action<TEventArgs>, Action, Action<Exception>> initiate,
CancellationToken token = default) where TEventHandler : Delegate
{
var tcs = new TaskCompletionSource<TEventArgs>();
Action<TEventArgs> complete = args => tcs.TrySetResult(args);
Action cancel = () => tcs.TrySetCanceled();
Action<Exception> reject = ex => tcs.TrySetException(ex);
TEventHandler handler = getHandler(complete, cancel, reject);
subscribe(handler);
try
{
using (token.Register(() => tcs.TrySetCanceled(),
useSynchronizationContext: false))
{
initiate(complete, cancel, reject);
return await tcs.Task;
}
}
finally
{
unsubscribe(handler);
}
}
}
This actually can be used to await any callback, not just event handlers, e.g.:
var mre = new ManualResetEvent(false);
RegisteredWaitHandle rwh = null;
await TaskExt.FromEvent<WaitOrTimerCallback, bool>(
(complete, cancel, reject) =>
(state, timeout) => { if (!timeout) complete(true); else cancel(); },
callback =>
rwh = ThreadPool.RegisterWaitForSingleObject(mre, callback, null, 1000, true),
callback =>
rwh.Unregister(mre),
(complete, cancel, reject) =>
ThreadPool.QueueUserWorkItem(state => { Thread.Sleep(500); mre.Set(); }),
CancellationToken.None);
Updated, less boilerplate for a simple event case (I use this one more often these days):
public static async Task<TEventArgs> FromEvent<TEventHandler, TEventArgs>(
Action<TEventHandler> subscribe,
Action<TEventHandler> unsubscribe,
CancellationToken token = default,
bool runContinuationsAsynchronously = true)
where TEventHandler : Delegate
where TEventArgs: EventArgs
{
var tcs = new TaskCompletionSource<TEventArgs>(runContinuationsAsynchronously ?
TaskCreationOptions.RunContinuationsAsynchronously :
TaskCreationOptions.None);
var handler = new Action<object?, TEventArgs>((_, args) => tcs.TrySetResult(args));
var h = (TEventHandler)Delegate.CreateDelegate(typeof(TEventHandler), handler.Target, handler.Method);
subscribe(h);
try
{
using (token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: false))
{
return await tcs.Task;
}
}
finally
{
unsubscribe(h);
}
}
Usage:
await TaskExt.FromEvent<FormClosedEventHandler, FormClosedEventArgs>(
h => mainForm.FormClosed += h,
h => mainForm.FormClosed -= h,
token);
Converting from EAP to Tasks is not that straightforward, mainly because you have to handle exceptions both when calling the long-running method and when handling the event.
The ParallelExtensionsExtras library contains the EAPCommon.HandleCompletion(TaskCompletionSource tcs, AsyncCompletedEventArgs e, Func getResult, Action unregisterHandler) extension method to make the conversion easier. The method handles subscribing/unsubscribing from an event. It doesn't try to start the long running operation as well
Using this method, the library implements asynchronous versions of SmtpClient, WebClient and PingClient.
The following method shows the general usage pattern:
private static Task<PingReply> SendTaskCore(Ping ping, object userToken, Action<TaskCompletionSource<PingReply>> sendAsync)
{
// Validate we're being used with a real smtpClient. The rest of the arg validation
// will happen in the call to sendAsync.
if (ping == null) throw new ArgumentNullException("ping");
// Create a TaskCompletionSource to represent the operation
var tcs = new TaskCompletionSource<PingReply>(userToken);
// Register a handler that will transfer completion results to the TCS Task
PingCompletedEventHandler handler = null;
handler = (sender, e) => EAPCommon.HandleCompletion(tcs, e, () => e.Reply, () => ping.PingCompleted -= handler);
ping.PingCompleted += handler;
// Try to start the async operation. If starting it fails (due to parameter validation)
// unregister the handler before allowing the exception to propagate.
try
{
sendAsync(tcs);
}
catch(Exception exc)
{
ping.PingCompleted -= handler;
tcs.TrySetException(exc);
}
// Return the task to represent the asynchronous operation
return tcs.Task;
}
The main difference from your code is here:
// Register a handler that will transfer completion results to the TCS Task
PingCompletedEventHandler handler = null;
handler = (sender, e) => EAPCommon.HandleCompletion(tcs, e, () => e.Reply,
() => ping.PingCompleted -= handler);
ping.PingCompleted += handler;
The extension method creates the handler and hooks the tcs. Your code sets the handler to the source object and starts the long operation. The actual handler type doesn't leak outside the method.
By separating the two concerns (handling the event vs starting the operation) it's easier to create a generic method.
Here is a solution that makes minimal use of reflection, inspired from the Observable.FromEvent method (Reactive Extensions).
public static Task<TEventArgs> TaskFromEvent<TDelegate, TEventArgs>(
Action<TDelegate> addHandler, Action<TDelegate> removeHandler)
where TDelegate : Delegate where TEventArgs : EventArgs
{
var tcs = new TaskCompletionSource<TEventArgs>();
TDelegate specificHandler = null;
Action<object, TEventArgs> handler = (sender, e) =>
{
removeHandler(specificHandler);
handler = null;
tcs.SetResult(e);
tcs = null;
};
var invokeMethodInfo = typeof(Action<object, TEventArgs>).GetMethod("Invoke");
specificHandler = (TDelegate)invokeMethodInfo
.CreateDelegate(typeof(TDelegate), handler);
addHandler(specificHandler);
return tcs.Task;
}
Usage example:
var documentCompletedAsync = TaskFromEvent<
WebBrowserDocumentCompletedEventHandler,
WebBrowserDocumentCompletedEventArgs>(
handler => webBrowser.DocumentCompleted += handler,
handler => webBrowser.DocumentCompleted -= handler);
webBrowser.Navigate("about:blank");
var url = (await documentCompletedAsync).Url;