【问题标题】:Calling an asynchronous method serially串行调用异步方法
【发布时间】:2010-10-11 18:50:13
【问题描述】:

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

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

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

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

有什么方法可以优雅地做到这一点? (我在 .Net 4.0 上)

我猜可能有一些方法可以通过继续传递来做到这一点......

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

【问题讨论】:

  • 您是否可以控制作为resultCallback 传递的方法?

标签: c# .net winforms asynchronous


【解决方案1】:

如果你将它包装在一个帮助器类中,你可以让帮助器“同步”你的值:

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 进程从一开始就同步,然后像上面一样将它移动到后台线程中,避免需要包装器。

【讨论】:

  • 感谢您的快速答复,我现在不在办公室,但我明天会尝试!只是一个问题 - 这个 .. 转换在另一个线程上的全部原因是“颤抖者”不断转动(winforms),WaitOne 会阻止吗?
  • @Benjol:是的 - 根据定义,使其同步将阻塞您调用它的线程。不过,您可以将整个进程移至后台线程(即:在后台线程上串行处理整个循环)...
  • @Benjol:我为此添加了另一个部分 - 希望能向您展示我的意思......
  • 谢谢@Reed。我也在玩另一种解决方案(见我的回答)。还没决定走哪条路,但你知道了:)
【解决方案2】:

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

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
}

一个明显的限制是堆栈溢出 - 就我的目的而言,这不是问题(我在数十个项目中,而不是数千个)。请在 cmets 中出现任何其他令人眼花缭乱的花絮!

【讨论】:

  • 我曾短暂地尝试在 IEnumerable(内置状态机)上执行此操作,但我认为不可能执行“顺风”异步。 Tomas Petricek 执行“逆风”异步 here
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-19
  • 2015-11-28
  • 2020-01-17
相关资源
最近更新 更多