What’s the point of this blog post? In modern C#/.NET ecosystem there is encouragement to use async/await Task based asynchronous model over other asynchronous patterns and approaches. On the other side, there are still a lot of libraries (legacy, wrappers from other technologies/languages which uses events for callback calls, etc…) which use older EAP approach to execute asynchronous code.
Very often, I am faced with situation where I use modern TAP async approach, but I need to reference some libraries still using Event-Based async pattern. Furthermore, these EAP-based calls need to be finished when callbacks are returned or – in some cases – they need to be canceled after some deterministic period of time.
I was eager to document this and to share my findings. Just to refresh, I wrote very similar blog post here: https://www.jenx.si/2019/10/02/c-from-event-based-asynchronous-pattern-to-task-based-asynchronous-pattern/, but it can’t harm if I publish a little more raw version of EAP to TAP migration.
Let’s take a look what TAP and EAPs are.
The task-based asynchronous pattern (TAP) is one of the asynchronous programming pattern which is based on the System.Threading.Tasks.Task and System.Threading.Tasks.Task<TResult> types in the System.Threading.Tasks namespace, which are used to represent arbitrary asynchronous operations. TAP is the recommended asynchronous design pattern for new development. On short: if you are using async/await and Task entities then you are using TAP async pattern. For more information, please checkout this link: https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap
EAP stands for Event-based Asynchronous Pattern which is the event-based legacy model for providing asynchronous behavior. It requires a method that starts the processing and and one or more events, event handler delegate types to handle processing finished callbacks. EAP was introduced in the .NET Framework 2.0 and it’s no longer recommended for new development. For more information, see Event-based Asynchronous Pattern (EAP).
Let’s take a look at very simple EAP example. I will create simple trigger which uses EAP pattern for handling asynchronous programming.
EAP example
In this section I will show simple EAP example. I created MyAwesomeTrigger
class which executes TriggerFired
event after defined period of time.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
using System; namespace Jenx.Eap2TapWithCancelation.Common { public class MyAwesomeEapTrigger { private readonly System.Timers.Timer _timer; public event EventHandler TriggerFired; public MyAwesomeEapTrigger(int triggerAfterMillisecs) { _timer = new System.Timers.Timer(triggerAfterMillisecs); _timer.Elapsed += OnTriggerFiredEvent; _timer.AutoReset = false; // execute only once } public void Start() { _timer.Enabled = true; // start trigger } private void OnTriggerFiredEvent(object sender, System.Timers.ElapsedEventArgs e) { TriggerFired?.Invoke(this, new EventArgs()); } } } |
I can use this trigger like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// new tigger instance var trigger = new MyAwesomeEapTrigger(4000); var wait = true; // wire-up event handlers... trigger.TriggerFired += (sender, args) => { System.Console.WriteLine($"EAP trigger executed at: {DateTime.Now:HH:mm:ss.ffff}..."); wait = false; }; // fire up the trigger... trigger.Start(); |
In Console, the output is as follows:
This is one of the “old practices” of asynchronous C# code execution. Thus, this kind of usage is not advised anymore in modern C# applications.
Modern C# code is using TAP (async/await, Task
& Task<TResult>
approach to asynchronous programming.
In the next section, I will briefly present simple TAP-based approach on asynchronous programming in C#.
TAP example
Below, analogues example as presented in previous chapter with Task-bsed async pattern.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
using System.Threading.Tasks; namespace Jenx.Eap2TapWithCancelation.Common { public class MyAwesomeTapTrigger { private readonly int _triggerAfterMillisecs; public MyAwesomeTapTrigger(int triggerAfterMillisecs) { _triggerAfterMillisecs = triggerAfterMillisecs; } public Task StartAsync() { return Task.Delay(_triggerAfterMillisecs); } } } |
I can use it as:
1 2 3 4 5 6 7 |
// new tigger instance var trigger = new MyAwesomeTapTrigger(4000); // fire up the trigger... System.Console.WriteLine($"TAP trigger started at: {DateTime.Now:HH:mm:ss.ffff}..."); await trigger.StartAsync(); System.Console.WriteLine($"TAP trigger executed at: {DateTime.Now:HH:mm:ss.ffff}..."); |
And my output is:
As said, TAP pattern is widely used in modern C# code.
I quickly presented both, EAP and TAP asynchronous code execution patterns. In the next section, I will present how to wrap/convert “old” EAP pattern into “modern” TAP based asynchronous pattern.
EAP to TAP
In the next section I will present one way how to wrap EAP example in TAP pattern with cancellation. Cancellation is very important, because in some cases your task can execute very long time, and you must be able to cancel asynchronous code execution if needed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
using System; using System.Threading; using System.Threading.Tasks; namespace Jenx.Eap2TapWithCancelation.Common { public class MyAwesomeEap2TapTrigger { public static async Task<string> ExecuteMyTrigerWitchCancellationAsync(int triggerTimeoutMilliSecs, CancellationToken cancellationToken) { // initialization string @result = ""; var tcs = new TaskCompletionSource<string>(cancellationToken); EventHandler triggerEventHandler = null; var timer = new MyAwesomeEapTrigger(triggerTimeoutMilliSecs); try { // first, define call back delegate triggerEventHandler += (connection, args) => { @result = $"my trigger fired at {DateTime.Now:HH:mm:ss.ffff}"; tcs.TrySetResult(@result); }; // wire up delegate with trgger event... timer.TriggerFired += triggerEventHandler; // start trigger timer.Start(); // fake exception to // throw new Exception(); // if cancelation is "executed" then inform executing task using (cancellationToken.Register(() => tcs.TrySetCanceled())) { await tcs.Task; } } catch (TaskCanceledException) { // i dont want exceptions in calling code @result = $"trigger canceled at {DateTime.Now:HH:mm:ss.ffff}"; // wanna cancelation exception in calling code? Use next line and return task at the end //tcs.TrySetCanceled(); } catch { @result = $"some exception happen while executing my trigger at {DateTime.Now:HH:mm:ss.ffff}"; } finally { timer.TriggerFired -= triggerEventHandler; } return @result; } } } |
Next source code excerpt shows how to consume upper EAP-to-TAP code.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
private static async Task ExecuteEap2TapWithCancelationDemo(int triggerValue, int timeoutValue) { var functionResult = ""; using (var cts = new System.Threading.CancellationTokenSource()) { cts.CancelAfter(timeoutValue); functionResult = await MyAwesomeEap2TapTrigger.ExecuteMyTrigerWitchCancellationAsync(triggerValue, cts.Token); } System.Console.WriteLine($"Timer trigger: {triggerValue}, timeout: {timeoutValue}," + $" result: {functionResult}"); } |
And, if I run the code I get expected results:
Conclusion
In this blog post I presented simple procedure/example how to convert C# EAP based async pattern to TAP-based one.
Asynchronous programming has never been easy, but with some good recipes and practices some task can be simplified and less error prone. I used similar approach as described in this blog post several times in my projects. So, if you find code in this blog post useful feel free to use it.
Happy coding.