views:

115

answers:

2

I have the following (simplified) asynchronous method:

void Transform<X,Y>(X x, Action<Y> resultCallback) {...}

and what I want to do is transform a list of Xs into a list of Ys.

The problem is that even though the Transform method is asynchronous, it has to be called serially (i.e. I have to wait for the callback before calling it with the next value).

Is there any way to do this elegantly? (I'm on .Net 4.0)

I'm guessing there might be some way to do it with continuation passing...

UPDATE I forgot to specify that I don't want to block the calling (GUI) thread.

+3  A: 

If you wrap this in a helper class, you could make the helper "synchronize" your values:

public class AsyncWrapper<X,Y>
{
    ManualResetEvent mre;
    private Y result; 

    public Y Transform(X x, YourClass yourClass)
    {
        mre = new ManualResetEvent(false);
        result = default(Y);

        yourClass.Transform<X,Y>(x, this.OnComplete);
        mre.WaitOne();
        return result;
    }

    void OnComplete(Y y)
    {
        result = y;
        mre.Set();
    }        
}

You could then use this like:

// instance your class with the Transform operation
YourClass yourClass = new YourClass();

AsyncWrapper<X,Y> wrapper = new AsyncWrapper<X,Y>();

foreach(X x in theXCollection)
{
     Y result = wrapper.Transform(x, yourClass);

     // Do something with result
}

Edit:

Since you say you're trying to do this to keep everything running on a background thread, you can use my code above, and do:

// Start "throbber"
Task.Factory.StartNew () =>
{
    // instance your class with the Transform operation
    YourClass yourClass = new YourClass();

    AsyncWrapper<X,Y> wrapper = new AsyncWrapper<X,Y>();

    foreach(X x in theXCollection)
    {
         Y result = wrapper.Transform(x, yourClass);

         // Do something with result
    }
}).ContinueWith( t =>
{
    // Stop Throbber
}, TaskScheduler.FromCurrentSynchronizationContext());

This will start the entire (now synchronous) process on a background thread, and disable your "throbber" (from comment) on the UI thread once it completes.

If you control all of this code, you can make your Transform process synchronous from the start, and just move it into a background thread as above, avoiding the need for the wrapper.

Reed Copsey
Thanks for the rapid answer, I'm not in the office right now, but I'll be trying it tomorrow! Just one question - the whole reason this .. transform is on another thread is so that the 'throbber' keeps turning (winforms), does the WaitOne block that?
Benjol
@Benjol: Yes - making it synchronous will, by definition, block the thread on which you call it. You can move your entire process to a background thread, though (ie: process the entire loop on a background thread serially)...
Reed Copsey
@Benjol: I added another section to this - hopefully to show you what I meant ...
Reed Copsey
Thanks @Reed. I'm also toying with another solution (see my answer). Haven't decided which way to go yet, but you get the tick :)
Benjol
+1  A: 

As I hinted in my question, I wondered about a solution using continuation-passing. The following extension methods allow me to have a fairly 'pretty' usage:

public static class Extensions
{
    //Using an asynchronous selector, calculate transform for 
    //  input list and callback with result when finished
    public static void AsyncSelect<TIn, TOut>(this List<TIn> list, 
              Action<TIn, Action<TOut>> selector, 
              Action<List<TOut>> callback)
    {
        var listOut = new List<TOut>();
        list.AsyncSelectImpl(listOut, selector, callback);
    }

    //internal implementation - hides the creation of the output list
    private static void AsyncSelectImpl<TIn, TOut>(this List<TIn> listIn, 
              List<TOut> listOut, 
              Action<TIn, Action<TOut>> selector, 
              Action<List<TOut>> callback)
    {
        if(listIn.Count == 0)
        {
            callback(listOut); //finished (also if initial list was empty)
        }
        else
        {
            //get first item from list, recurse with rest of list
            var first = listIn[0];
            var rest = listIn.Skip(1).ToList();
            selector(first, result => { 
                            listOut.Add(result); 
                            rest.AsyncSelectImpl(listOut, selector, callback); 
                    });
        }
    }
}

On the calling side, this results in:

    (...)
    //(For a Transform which performs int -> string)
    Throbber.Start();
    inList.AsyncSelect<int,string>(Transform, WhenDone);
}
private void WhenDone(List<string> outList)
{
    Throbber.Stop();
    //do something with outList
}

One obvious limitation is stack overflow - for my purposes, that won't be a problem (I'm in the tens of items, not thousands). Any other glaring bloopers in the comments please!

Benjol
I flirted briefly with trying to do this on an IEnumerable (built-in state machine), but I don't think it's possible to do 'downwind' async. [Tomas Petricek](http://stackoverflow.com/users/33518/tomas-petricek) does 'upwind' async [here](http://tomasp.net/blog/csharp-async.aspx)
Benjol