串行调用异步方法

时间:2010-10-11 18:50:14

标签: c# .net winforms asynchronous

我有以下(简化)异步方法:

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

我想要做的是将X列表转换为Y列表。

问题在于,即使Transform方法是异步的,也必须以串行方式调用它(即我必须在使用下一个值调用它之前等待回调)。

有没有办法优雅地做到这一点? (我在.Net 4.0上)

我猜测继续传递可能有某种方法可以做到这一点......

更新我忘了指定我不想阻止调用(GUI)线程。

2 个答案:

答案 0 :(得分: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”(来自注释)。

如果您控制所有这些代码,您可以从一开始就使您的转换过程同步,并将其移动到如上所述的后台线程中,从而避免使用包装器。

答案 1 :(得分:1)

正如我在我的问题中暗示的那样,我想知道使用延续传递的解决方案。以下扩展方法允许我使用相当“漂亮”的用法:

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
}

一个明显的限制是堆栈溢出 - 对于我的目的,这不会是一个问题(我在数十项,而不是数千)。请评论中任何其他明显的蠢货!