按顺序调用异步方法

3
我有如下(简化版)的异步方法:
void Transform<X,Y>(X x, Action<Y> resultCallback) {...}

我想要做的是将一组X转换为一组Y。
问题在于,即使Transform方法是异步的,它也必须按顺序调用(即在调用下一个值之前必须等待回调)。
有没有什么优雅的方式来解决这个问题?(我使用的是.Net 4.0)
我猜想可能可以使用继续传递方式来解决...
更新:我忘记指定不想阻塞调用(GUI)线程。

你能控制作为“resultCallback”传递的方法吗? - Matt Mills
2个回答

4
如果您将此内容封装在一个辅助类中,您可以使该辅助类“同步”您的值。
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();
    }        
}

您可以像这样使用它:
// 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
}

编辑:

既然你说你想要将所有东西都在后台线程上运行,你可以使用我上面的代码,并进行以下操作:

// 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());

这将在后台线程上启动整个(现在同步的)过程,并在完成后禁用UI线程中的“throbber”(来自评论)。

如果您控制所有这些代码,可以从一开始就使您的Transform过程同步,并将其移入后台线程,避免需要包装器。


谢谢您的快速回复,我现在不在办公室,但明天我会尝试!只有一个问题 - 这个..转换在另一个线程上的整个原因是为了让“throbber”保持转动(winforms),WaitOne会阻塞它吗? - Benjol
@Benjol:是的 - 将其变成同步将会阻塞您调用它的线程。不过,您可以将整个进程移动到后台线程中(即:在后台线程上串行处理整个循环)... - Reed Copsey
@Benjol:我添加了另一部分内容,希望能向您展示我的意思... - Reed Copsey
感谢 @Reed。我还在尝试另一种解决方案(请看我的回答)。还没有决定走哪条路,但你会得到确认 :) - Benjol

1
正如我在问题中暗示的那样,我想知道是否有一种使用 continuation-passing 的解决方案。以下扩展方法使我能够拥有相当“漂亮”的用法:
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); 
                    });
        }
    }
}

在调用方面,这将导致:
    (...)
    //(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
}

一个明显的限制是堆栈溢出 - 对于我的目的,这不会成为问题(我只有几十个项目,而不是数千个)。如果评论中还有其他明显的错误,请指出!


我曾尝试使用IEnumerable(内置状态机)来实现这一点,但我认为不可能进行“下游”异步操作。Tomas Petricek在此处进行了“上游”异步操作。这里 - Benjol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接