Last active
January 30, 2023 14:35
-
-
Save OlegKarasik/90c2355e3e170a0885bd06874183428a to your computer and use it in GitHub Desktop.
Code Tip: How to work with asynchronous event handlers in C#?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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