Task-based Asynchronous Pattern (TAP) was introduced in .NET Framework 4 and since then, it is the recommended approach to asynchronous programming in .NET. Based on System.Threading.Tasks.Task and async and await keywords/operators to handle asynchronous programming in C#.
Even more, newest C# code is more or less all over “populated” with async/await(s). Hence, If you write modern C# code, you definitively already stumbled at async/await
.
On the other hand, older Event-based Asynchronous Pattern (EAP) is still in use, especially at legacy systems, but for new development is not recommended anymore. The idea behind EAP is to register to event and wait for the callback from processing/asynchronous code.
In this blog post I will show how to “migrate” from EAP to TAP pattern on a simple example. Of course, asynchronous programming is broad topic, here I will just cover simple – but the most generic case – how to switch from EAP to TAP.
EAP Example
Lets look at this simple code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
using System; using System.Threading; namespace Jenx.Event2Async { public class EventBasedTriggerManager { public event EventHandler OnTriggerExecuted; public void Start(int timeout = 5000) { var timer = new Timer(new TimerCallback((state) => { OnTriggerExecuted?.Invoke(null, null); })); timer.Change(timeout, 0); } } } |
Logic here is very simple: EventBasedTriggerManager
triggers event after some timeout upon Start()
is called. I can register to this event and get callback when action is invoked. To be more illustrative, let’s check next code:
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 |
using System; namespace Jenx.Event2Async { internal class Program { private static void Main() { Console.WriteLine($"Program started at: {DateTime.Now.ToLongTimeString()}"); // create instance var trigger = new EventBasedTriggerManager(); // register to event/callback trigger.OnTriggerExecuted += (sender, e) => { Console.WriteLine($"Trigger manager triggered at: { DateTime.Now.ToLongTimeString()}"); Console.WriteLine("Hit key to exit"); }; // start processing trigger.Start(); Console.WriteLine($"Start waiting at {DateTime.Now.ToLongTimeString()}"); Console.WriteLine($"Wait, processing..."); Console.ReadLine(); } } } |
My super awesome app returns this:
Everything clear here: I registered my event handler to wait for event to get triggered. It would be super awesome, if I could rewrite this as async/await. So, let’s get our hands dirty.
TAP Example (wrapper)
The idea is to wrap upper EAP code into TAP.
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 |
using System; using System.Threading; using System.Threading.Tasks; namespace Jenx.Event2Async { public class TaskBasedTriggerManager { private EventBasedTriggerManager _eventBasedTriggerManager = new EventBasedTriggerManager(); private int _timeout = 5000; public async Task StartAsync(int timeout = 5000) { _timeout = timeout; // to be more generic, let's also introduce mechanism for timeout, // which is generally a good idea, not to lock me out. using var cts = new CancellationTokenSource(); cts.CancelAfter(new TimeSpan(0, 0, 0, 15)); // ok, after 15 sec we ant to cancel task await InternalStartAsync(cts.Token); // let's roll } private async Task InternalStartAsync(CancellationToken cancellationToken) { var tcs = new TaskCompletionSource<bool>(cancellationToken); EventHandler eventTriggerEventHandler = null; try { eventTriggerEventHandler += (connection, args) => { Console.WriteLine($"Event Triggered at: { DateTime.Now.ToLongTimeString()}"); tcs.TrySetResult(true); }; _eventBasedTriggerManager.OnTriggerExecuted += eventTriggerEventHandler; _eventBasedTriggerManager.Start(_timeout); using (cancellationToken.Register(() => { tcs.TrySetCanceled(); })) { await tcs.Task; } } catch (TaskCanceledException) { Console.WriteLine($"Timeout"); tcs.TrySetResult(false); } catch { Console.WriteLine($"some other issue, not timeout."); tcs.TrySetResult(false); } finally { _eventBasedTriggerManager.OnTriggerExecuted -= eventTriggerEventHandler; } } } } |
My calling code is now rewritten into:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
using System; using System.Threading.Tasks; namespace Jenx.Event2Async { internal class Program { private static async Task Main() { Console.WriteLine($"Program started at: {DateTime.Now.ToLongTimeString()}"); // create instance of task based manager (wrapper) var trigger = new TaskBasedTriggerManager(); Console.WriteLine("Waiting to execution..."); await trigger.StartAsync(); Console.WriteLine("Hit key to exit"); Console.ReadLine(); } } } |
You can see async call to Task based function trigger.StartAsync()
, and the result is like this:
But what happens when async gets out of timeout! Let’s reduce timeout to 2 sec (trigger is executed after 5 sec, by default). I just changed TimeSpan
in CancelAfter()
method on CancellationTokenSource
object, e.g.
1 |
cts.CancelAfter(new TimeSpan(0, 0, 0, 3)); // ok, after 3 sec we want to cancel task |
The output is as expected, I get timeout.
Voila, we have it: simple EAP to TBP wrapper.
Conclusion
I shared simple example how to rewrite/wrap event-based asynchronous code to task-based asynchronous approach. It’s not silver bullet, but in many cases you can use similar approach.
If you have similar problems/solutions give me a ping and share your ideas.
Happy coding!
One thought on “C#: From Event-based Asynchronous Pattern to Task-based Asynchronous Pattern”
thank you, very good example.
It would be usefull to also show how to return a value from the event up the chain.