Reputation: 2200
Assume, I have the IAsynchronous
interface that supports two methods for doing some operation ("begin/end" pattern):
IAsyncResult BeginOperation(AsyncCallback callback, object state)
EndOperation(IAsyncResult ar)
Assume also, that I have the classes A : IAsynchronous
and B : IAsynchronous
. I have to implement the Compound : IAsynchronous
class. The Compound
's operation is to invoke A
's operation, wait for its completion, then to invoke B
's operation and then to invoke the callback as usual.
The issue is how to design the Compound
class:
state
object and invoke the original callback when the Compound
's operation completes. Thus, I can not simply pass them to A.BeginOperation
or B.BeginOperation
.Compound.EndOperation
should re-throw any exception that's thrown by A.EndOperation
or B.EndOperation
. If A.EndOperation
has thrown an exception, then the Compound
's operation should not invoke B.EndOperation
because the compound operation is already failed.To clarify, consider the following example. Assume, that we have the Multiplier
class that supports the following methods:
IAsyncResult BeginMultiply(LargeNumber x, LargeNumber y, AsyncCallback callback, object state)
LargeNumber EndMultiply(IAsyncResult ar)
And you want to write the ThreeMultiplier
class that supports the following methods:
IAsyncResult BeginMultiply(LargeNumber x, LargeNumber y, LargeNumber z, AsyncCallback callback, object state)
LargeNumber EndMultiply(IAsyncResult ar)
The ThreeMultiplier
class should use the Multiplier
class to compute x * y * z
. In order to do this it first computes x * y
(through Multiplier.Begin/EndMultiply
) and then multiplies the result by z
. Of course, Multiplier.EndMultiply
can throw SomeException
which fails computing of x * y * z
at any step.
What is the best (or good) way to implement this? Is there any pattern?
Upvotes: 2
Views: 499
Reputation: 35881
I would avoid the APM (asynchronous programming model: use of IAsyncResult and Begin* and End*) when writing new code.
In Visual Studio 2010 the Task Parallel Library (TPL) was introduced at which time the Task Asynchronous Pattern (TAP) was introduced. This pattern is the basis for the underlying framework APIs that support the new async/await keywords in VS 2012 (C# 5). You can wrap APM implementations with Task.FromAsync(); but if you're writing new code then using Task/Task would be a better choice for the future.
With TAP, you wrap a delegate with a Task object that will execute the delegate asynchronously. You can then "continue" with other asynchronous tasks that will run when the first delegate completes. For example, if you had two delegates where one needs to run at the completion of the other, you could do this:
Task.Factory.StartNew(() => MyMethod())
.ContinueWith(() => MyOtherMethod());
You could wrap that in a single method:
public void AsyncCompound(Action firstAcction, Action secondAction)
{
Task.Factory.StartNew(firstAction)
.ContinueWith(secondAction);
}
...much less work that defining IAsyncResult classes and implementing both a Begin and End method.
As for .NET 3.5--the prevailing pattern is APM-there is no "Task" class consistently available. There's some TPL implementations that might work, but I haven't used them. Alternatively you could look into Reactive Extensions as it's another way of implementing asynchronous operations--although event-based.
APM can get verbose fast, so, I'd recommend using delegates wherever you can. I'd also recommend re-using an IAsyncResult implemention like the one at http://msdn.microsoft.com/en-us/magazine/cc163467.aspx For example:
public class Multiplier
{
public LargeNumber Multiply(LargeNumber x, LargeNumber y)
{
return x * y;
}
public IAsyncResult BeginMultiply(LargeNumber x, LargeNumber y, AsyncCallback callback, object state)
{
AsyncResult<LargeNumber> ar = new AsyncResult<BigInteger>(callback, state);
ThreadPool.QueueUserWorkItem(o =>
{
var asyncResult = (AsyncResult<LargeNumber>)o;
try
{
var largeNumber = Multiply(x, y);
asyncResult.SetAsCompleted(largeNumber, false);
}
catch (Exception e)
{
asyncResult.SetAsCompleted(e, false);
}
}, ar);
return ar;
}
public LargeNumber EndMultiply(IAsyncResult asyncResult)
{
var ar = (AsyncResult<LargeNumber>)asyncResult;
return ar.EndInvoke();
}
public IAsyncResult BeginMultiply(LargeNumber x, LargeNumber y, LargeNumber z, AsyncCallback callback, object state)
{
AsyncResult<LargeNumber> ar = new AsyncResult<LargeNumber>(callback, state);
BeginMultiply(x, y, (asyncResult1) =>
{
var firstResult = EndMultiply(asyncResult1);
BeginMultiply(firstResult, z, (asyncResult2) =>
{
var secondResult = EndMultiply(asyncResult2);
ar.SetAsCompleted(secondResult, true);
}, state);
}, state);
return ar;
}
}
Which could then be used as follows to asynchronously calculate a value and return to the current thread:
var asyncResult = multiplier.BeginMultiply(x, y, z, ar => { }, null);
var result = multiplier.EndMultiply(asyncResult);
Or, you could chain off to other code to be executed on a background thread:
multiplier.BeginMultiply(x, y, z, ar =>
{
var result = multiplier.EndMultiply(ar);
/* TODO: something with result on this background thread */
}, null);
... it would be up to you to decide how you'd get that result where it needs to go and how that interacts with the thread that called BeginMultiply, if at all.
Upvotes: 4
Reputation: 100527
If you want to implement some other nice way of combining asynchronous operations - check out Richter's Simplified APM With The AsyncEnumerator and next part which allows to write almost sequential code using iterators created with yield
.
The library download from Wintellect site.
Usage sample - Working with AsyncEnumerator
Upvotes: 1