This class acts as an asynchronous event handler that will execute all attached tasks in an async/await context. Requires Nuget Immutables
. Example usage:
class MyEventArgs : EventArgs {}
async Task SomeAsyncMethodAobject src, EventArgs args) {
Console.WriteLine("Task A...");
await Task.Delay(2000);
}
async Task SomeAsyncMethodB(object src, EventArgs args) {
Console.WriteLine("Task B...");
await Task.Delay(1000);
}
static async Task Main(string[] args) {
AsyncEvent<MyEventArgs> Events;
Events = new AsyncEvent<MyEventArgs>();
Events += SomeAsyncMethodA;
Events += SomeAsyncMethodB;
await Events?.InvokeAsync(this, new MyEventArgs());
// Use below to discard task and not await event task to finish.
// _ = Events?.InvokeAsync(this, new MyEventArgs()).ConfigureAwait(false);
}
Source for the AsyncEvent<EventArgsT>
class:
// T is the EventArgs class type to pass to the callbacks on Invoke.
public class AsyncEvent<T> where T : EventArgs {
// List of task methods to await.
public ImmutableList<Func<object, T, Task>> Invokables;
// on += add new callback method to AsyncEvent.
public static AsyncEvent<T> operator+(AsyncEvent<T> source, Func<object, T, Task> callback) {
if (callback == null) throw new NullReferenceException("Callback is null! <AsyncEvent<T>>");
if (source == null) return null;
if (source.Invokables == null) source.Invokables = ImmutableList<Func<object, T, Task>>.Empty;
source.Invokables = source.Invokables.Add(callback);
return source;
}
// on -= remove existing callback from AsyncEvent.
public static AsyncEvent<T> operator -(AsyncEvent<T> source, Func<object, T, Task> callback) {
if (callback == null) throw new NullReferenceException("Callback is null! <AsyncEvent<T>>");
if (source == null) return null;
source.Invokables = source.Invokables.Remove(callback);
return source;
}
// Invoke the tasks asynchronously with a cancelation token.
public async Task InvokeAsync(object source, T evArgs, CancellationToken token) {
List<Task> tasks = new List<Task>();
if (Invokables != null)
foreach (var callback in Invokables)
if (!token.IsCancellationRequested)
tasks.Add(callback(source, evArgs));
await Task.WhenAll(tasks.ToArray());
}
// Invoke the tasks asynchronously.
public async Task InvokeAsync(object source, T evArgs) {
List<Task> tasks = new List<Task>();
if (Invokables != null)
foreach (var callback in Invokables)
tasks.Add(callback(source, evArgs));
await Task.WhenAll(tasks.ToArray());
}
}
Is there anything wrong with this asynchronous paradigm?
1 Answer 1
Not a review but an alternative solution to leave here. Just because the above one is overkill, as for me.
Probably you want to implement Publisher/Subscriber pattern in awaitable/Command mode. But it's possible using delegate itself.
Consider the extension method
public static class DelegateExtensions
{
public static Task InvokeAsync<TArgs>(this Func<object, TArgs, Task> func, object sender, TArgs e)
{
return func == null ? Task.CompletedTask
: Task.WhenAll(func.GetInvocationList().Cast<Func<object, TArgs, Task>>().Select(f => f(sender, e)));
}
}
And the test
class Program
{
public static event Func<object, EventArgs, Task> MyAsyncEvent;
static async Task SomeAsyncMethodA(object src, EventArgs args)
{
Console.WriteLine("Task A...");
await Task.Delay(2000);
Console.WriteLine("Task A finished");
}
static async Task SomeAsyncMethodB(object src, EventArgs args)
{
Console.WriteLine("Task B...");
await Task.Delay(1000);
Console.WriteLine("Task B finished");
}
static async Task Main(string[] args)
{
MyAsyncEvent += SomeAsyncMethodA;
MyAsyncEvent += SomeAsyncMethodB;
await MyAsyncEvent.InvokeAsync(null, EventArgs.Empty);
Console.WriteLine("Invoked");
Console.ReadKey();
}
}
Output
Task A...
Task B...
Task B finished
Task A finished
Invoked
Looks like it works.
-
\$\begingroup\$ I like the solution. One problem though is that
Delegate[]
does not contain a definition for*.Cast
. \$\endgroup\$FatalSleep– FatalSleep2021年06月01日 00:19:29 +00:00Commented Jun 1, 2021 at 0:19 -
1\$\begingroup\$ @FatalSleep add
using System.Linq;
\$\endgroup\$aepot– aepot2021年06月01日 05:58:46 +00:00Commented Jun 1, 2021 at 5:58 -
\$\begingroup\$ thank you. Also how did you find
DelegateExtensions
? I've search the internet and cannot find any info on it. \$\endgroup\$FatalSleep– FatalSleep2021年06月02日 06:16:08 +00:00Commented Jun 2, 2021 at 6:16 -
1\$\begingroup\$ @FatalSleep
this Func<object, TArgs, Task> func
<--- this type marked asthis
. That's not for delegates but only for this type. \$\endgroup\$aepot– aepot2021年06月02日 07:29:05 +00:00Commented Jun 2, 2021 at 7:29 -
1\$\begingroup\$ Beautiful solution! \$\endgroup\$Arthur Nunes– Arthur Nunes2022年10月20日 14:39:59 +00:00Commented Oct 20, 2022 at 14:39
AsyncEvent
do you have? What jobs are they doing on the event was fired? What data is passed by Args? How often the event is fired, ~times/sec, peak/avg? Why I'm asking: It looks like you've solved some problem in a wrong way. \$\endgroup\$