Question

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:

  • I should preserve the original 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.
  • Also, 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.
  • What if there will be not only 2 inner operations, but 3 or more? Is there a common way?

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?

Was it helpful?

Solution

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.

OTHER TIPS

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

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top