Skip to content

Instantly share code, notes, and snippets.

@OlegKarasik
Last active January 30, 2023 14:35
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save OlegKarasik/90c2355e3e170a0885bd06874183428a to your computer and use it in GitHub Desktop.
Save OlegKarasik/90c2355e3e170a0885bd06874183428a to your computer and use it in GitHub Desktop.
Code Tip: How to work with asynchronous event handlers in C#?
public class Demo
{
public event EventHandler DemoEvent;
public void Raise()
{
this.DemoEvent?.NaiveRaiseAsync(this, EventArgs.Empty).GetAwaiter().GetResult();
Console.WriteLine("All handlers have been executed!");
}
}
public static class NaiveExtension
{
public static Task NaiveRaiseAsync(
this EventHandler @this,
object sender,
EventArgs eventArgs)
{
if (@this is null)
{
return Task.CompletedTask;
}
var tcs = new TaskCompletionSource<bool>();
var delegates = @this.GetInvocationList();
var count = delegates.Length;
var exception = (Exception)null;
foreach (var @delegate in @this.GetInvocationList())
{
var async = @delegate.Method
.GetCustomAttributes(typeof(AsyncStateMachineAttribute), false)
.Any();
var completed = new Action(() =>
{
if (Interlocked.Decrement(ref count) == 0)
{
if (exception is null)
{
tcs.SetResult(true);
}
else
{
tcs.SetException(exception);
}
}
});
var failed = new Action<Exception>(e =>
{
Interlocked.CompareExchange(ref exception, e, null);
});
if (async)
{
SynchronizationContext.SetSynchronizationContext(
new NaiveSynchronizationContext(completed, failed));
}
try
{
@delegate.DynamicInvoke(sender, eventArgs);
}
catch (TargetInvocationException e) when (e.InnerException != null)
{
// When exception occured inside Delegate.Invoke method all exceptions are wrapped in
// TargetInvocationException.
failed(e.InnerException);
}
catch (Exception e)
{
failed(e);
}
if (!async)
{
completed();
}
}
return tcs.Task;
}
}
public class NaiveSynchronizationContext :
SynchronizationContext
{
private readonly Action completed;
private readonly Action<Exception> failed;
public NaiveSynchronizationContext(
Action completed,
Action<Exception> failed)
{
this.completed = completed;
this.failed = failed;
}
public override SynchronizationContext CreateCopy()
{
return new NaiveSynchronizationContext(
this.completed,
this.failed);
}
public override void Post(SendOrPostCallback d, object state)
{
if (state is ExceptionDispatchInfo edi)
{
Console.WriteLine("Capturing Exception");
this.failed(edi.SourceException);
}
else
{
Console.WriteLine("Posting");
base.Post(d, state);
}
}
public override void Send(SendOrPostCallback d, object state)
{
if (state is ExceptionDispatchInfo edi)
{
Console.WriteLine("Capturing Exception");
this.failed(edi.SourceException);
}
else
{
Console.WriteLine("Sending");
base.Send(d, state);
}
}
public override void OperationStarted()
{
Console.WriteLine("SynchronizationContext: Started");
}
public override void OperationCompleted()
{
Console.WriteLine("SynchronizationContext: Completed");
this.completed();
}
}
class Program
{
static void Main(string[] args)
{
var instance = new Demo();
instance.DemoEvent += async (
sender,
eventArgs) =>
{
await Task.Delay(10);
Console.WriteLine("Executed!");
};
instance.DemoEvent += async (
sender,
eventArgs) =>
{
await Task.Delay(200);
Console.WriteLine("One more Executed!");
};
instance.DemoEvent += (
sender,
eventArgs) =>
{
Console.WriteLine("He-he, one more Executed!");
};
instance.DemoEvent += async (
sender,
eventArgs) =>
{
await Task.Delay(50);
throw new InvalidOperationException("Sabotage!");
};
instance.DemoEvent += (
sender,
eventArgs) =>
{
throw new InvalidOperationException("Synchronous Sabotage!");
};
try
{
instance.Raise();
}
catch (Exception e)
{
Console.WriteLine($"Exception: - {e.Message}");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment