This topic describes how to batch multiple asynchronous tasks with coroutines using function lists. You will likely use this technique when writing coroutines in Visual Basic.NET.
It's also useful in C# when you want to build up a list of things to do asynchronously rather than define all those tasks within a single iterator method.
The iterator approach to batching asynchronous tasks is a popular choice in C#.
That choice is not available to Visual Basic.NET programmers because iterators aren't implemented in VB.NET.
Fortunately, you can still use the DevForce Coroutine class in VB to manage a sequence of asynchronous tasks. Instead of writing an iterator to produce the sequence, you construct a list of functions that return INotifyCompleted and pass the list to the Coroutine.
We call this the "function list approach" to coroutines.
The function list approach is also useful in C# when you want to build up a list of things to do asynchronously rather than define all those tasks within a single iterator method.
The basic recipe is as follows
Here is an example that loads all customers, updates them, and saves them back to the database.
LoadAllCustomersUpdateAndSaveCore is the "batcher" in our example.
C# | private IEnumerable<Func<INotifyCompleted>> LoadAllCustomersUpdateAndSaveCore(NorthwindEntities manager) { // List of asynchronous functions for the Coroutine to execute serially. var funcList = new List<Func<INotifyCompleted>>(); // Get all customers funcList.Add(() => manager.Customers.ExecuteAsync()); // Make a change to them and save them funcList.Add(() => { UpdateAllCachedCustomers(manager); // sync operation to update customers in cache - for example return manager.SaveChangesAsync(); // asynchronous operation returning INotifyCompleted }); // return the list return funcList; } |
VB | Private Function LoadAllCustomersUpdateAndSaveCore(ByVal manager As NorthwindEntities) _ As IEnumerable(Of Func(Of INotifyCompleted)) ' List of asynchronous functions for the Coroutine to execute serially. Dim funcList = New List(Of Func(Of INotifyCompleted)) ' Get all customers Dim loadFnc As Func(Of INotifyCompleted) = Function() Return manager.Customers.ExecuteAsync() ' return an INotifyCompleted End Function funcList.Add(loadFnc) ' Make a change to them and save them Dim saveFnc As Func(Of INotifyCompleted) = Function() UpdateAllCachedCustomers(manager) ' sync operation to update customers in cache - for example Return manager.SaveChangesAsync() ' return an INotifyCompleted End Function funcList.Add(saveFnc) ' return the list Return funcList End Function |
Observations:
The Coroutine.Start method of the DevForce Coroutine class will accept either an iterator or a list of asynchronous functions. The Coroutine works the same in either case, pausing until each asynchronous function completes.
C# | var op = Coroutine.Start(LoadAllCustomersUpdateAndSaveCore(manager)); op.Completed += (s, e) => { if (e.CompletedSuccessfully) { ShowMessage("All customers were updated"); } else { ShowMessage(e.Error.Message); } }; |
VB | Dim op = Coroutine.Start(LoadAllCustomersUpdateAndSaveCore(manager)) AddHandler op.Completed, Sub(s As Object, e As CoroutineCompletedEventArgs) If e.CompletedSuccessfully Then ShowMessage("All customers were updated") Else ShowMessage(e.Error.Message) End If End Sub |
We can adopt a similar approach to run a collection of asynchronous tasks in parallel.
Imagine a batcher called LoadReferenceEntitiesCore that asynchronously loaded sets of reference entities such as colors, states, unit types, etc. You would write this in precisely the same way that you did LoadAllCustomersUpdateAndSaveCore, the only difference would be in the functions that you added to the funcList.
We'd call StartParallel instead of Start as in this example, which also uses a callback instead of an event handler.
C# | Coroutine.StartParallel( LoadAllCustomersUpdateAndSaveCore(manager), // batcher (op) => // callback { if (op.CompletedSuccessfully) { ShowMessage("References entities loaded"); } else { ShowMessage(op.Error.Message); } } ); |
VB | Coroutine.StartParallel( _ LoadReferenceEntitiesCore(manager), _ ' batcher Sub(op As CoroutineOperation) ' callback If op.CompletedSuccessfully Then ShowMessage("Reference entities loaded") Else ShowMessage(op.Error.Message) End If End Sub ) |