I have the following (simplified) asynchronous method:
void Transform<X,Y>(X x, Action<Y> resultCallback) {...}
开发者_JAVA百科
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.
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.
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!
精彩评论