如何使这个异步方法调用工作?

时间:2010-11-15 09:56:01

标签: c# asynchronous pipeline

我正在尝试使用异步方法调用开发方法管道。管道的逻辑如下

  1. 集合中有n个数据必须输入管道中的m个方法
  2. 枚举T
  3. 的集合
  4. 将第一个元素提供给第一个方法
  5. 获取输出,以异步方式将其提供给第二种方法
  6. 同时,将集合的第二个元素提供给第一个方法
  7. 完成第一个方法后,将结果提供给第二个方法(如果第二个方法仍在运行,将结果放入队列并在第一个方法开始执行第三个元素)
  8. 当第二个方法完成执行时,从队列中取出第一个元素并执行等等(每个方法应该异步运行,没有人应该等待下一个完成)
  9. 在第m个方法中,执行数据后,将结果存储到列表
  10. 在第m个方法完成第n个元素后,将结果列表(n个结果)返回到第一个级别。
  11. 我提出了如下代码,但它没有按预期工作,结果永远不会被返回,而且它没有按顺序执行。

    static class Program
        {
            static void Main(string[] args)
            {
                var list = new List<int> { 1, 2, 3, 4 };
                var result = list.ForEachPipeline(Add, Square, Add, Square);
                foreach (var element in result)
                {
                    Console.WriteLine(element);
                    Console.WriteLine("---------------------");
                }
                Console.ReadLine();
            }
    
            private static int Add(int j)
            {
                return j + 1;
            }
    
            private static int Square(int j)
            {
                return j * j;
            }
    
            internal static void AddNotify<T>(this List<T> list, T item)
            {
                Console.WriteLine("Adding {0} to the list", item);
                list.Add(item);
            }    
        }
    
        internal class Function<T>
        {
            private readonly Func<T, T> _func;
    
            private readonly List<T> _result = new List<T>();
            private readonly Queue<T> DataQueue = new Queue<T>();
            private bool _isBusy;
            static readonly object Sync = new object();
            readonly ManualResetEvent _waitHandle = new ManualResetEvent(false);
    
            internal Function(Func<T, T> func)
            {
                _func = func;
            }
    
            internal Function<T> Next { get; set; }
            internal Function<T> Start { get; set; }
            internal int Count;
    
            internal IEnumerable<T> Execute(IEnumerable<T> source)
            {
                var isSingle = true;
                foreach (var element in source) {
                    var result = _func(element);
                    if (Next != null)
                    {
                        Next.ExecuteAsync(result, _waitHandle);
                        isSingle = false;
                    }
                    else
                        _result.AddNotify(result);
                }
                if (!isSingle)
                    _waitHandle.WaitOne();
                return _result;
            }
    
    
            internal void ExecuteAsync(T element, ManualResetEvent resetEvent)
            {
                lock(Sync)
                {
                    if(_isBusy)
                    {
                        DataQueue.Enqueue(element);
                        return;
                    }
                    _isBusy = true;
    
                    _func.BeginInvoke(element, CallBack, resetEvent);
                }           
            }
    
            internal void CallBack(IAsyncResult result)
            {
                bool set = false;
                var worker = (Func<T, T>) ((AsyncResult) result).AsyncDelegate;
                var resultElement = worker.EndInvoke(result);
                var resetEvent = result.AsyncState as ManualResetEvent;
    
                lock(Sync)
                {
                    _isBusy = false;
                    if(Next != null)
                        Next.ExecuteAsync(resultElement, resetEvent);
                    else
                        Start._result.AddNotify(resultElement);
    
                    if(DataQueue.Count > 1)
                    {
                        var element = DataQueue.Dequeue();
                        ExecuteAsync(element, resetEvent);
                    }
                    if(Start._result.Count == Count)
                        set = true;
                }
                if(set)
                  resetEvent.Set();
            }
        }
    
        public static class Pipe
        {
            public static IEnumerable<T> ForEachPipeline<T>(this IEnumerable<T> source, params Func<T, T>[] pipes)
            {
                Function<T> start = null, previous = null;
                foreach (var function in pipes.Select(pipe => new Function<T>(pipe){ Count = source.Count()}))
                {
                    if (start == null)
                    {
                        start = previous = function;
                        start.Start = function;
                        continue;
                    }
                    function.Start = start;
                    previous.Next = function;
                    previous = function;
                }
                return start != null ? start.Execute(source) : null;
            }
        }
    

    你能帮助我让这件事有用吗?如果这种设计不适合实际的方法管道,请随意提出不同的方法。

    编辑:我必须严格遵守.Net 3.5。

3 个答案:

答案 0 :(得分:1)

采取管道接近的任何特殊原因? IMO,为每个输入启动一个单独的线程,所有函数一个接一个地链接起来,编写起来更简单,执行起来更快。例如,

function T ExecPipe<T>(IEnumerable<Func<T, T>> pipe, T input)
{
  T value = input;
  foreach(var f in pipe)
  {
    value = f(value);
  }
  return value;
}

var pipe = new List<Func<int, int>>() { Add, Square, Add, Square };
var list = new List<int> { 1, 2, 3, 4 };
foreach(var value in list)
{
  ThreadPool.QueueUserWorkItem(o => ExecPipe(pipe, (int)o), value);
}

现在,来到你的代码,我相信对于M阶段的准确流水线实现,你必须有正好M个线程,因为每个阶段可以并行执行 - 现在,一些线程可能因为i / p没有到达它们而空闲。我不确定你的代码是否正在启动任何线程,特定时间线程的数量是多少。

答案 1 :(得分:1)

我没有立即在您的代码中发现问题,但您可能会过度复杂化一些事情。这可能是一种更简单的方法来做你想做的事。

public static class Pipe 
{
   public static IEnumerable<T> Execute<T>(
      this IEnumerable<T> input, params Func<T, T>[] functions)
   {
      // each worker will put its result in this array
      var results = new T[input.Count()];

      // launch workers and return a WaitHandle for each one
      var waitHandles = input.Select(
         (element, index) =>
         {
            var waitHandle = new ManualResetEvent(false);
            ThreadPool.QueueUserWorkItem(
               delegate
               {
                  T result = element;
                  foreach (var function in functions)
                  {
                     result = function(result);
                  }
                  results[index] = result;
                  waitHandle.Set();
               });
            return waitHandle;
         });

      // wait for each worker to finish
      foreach (var waitHandle in waitHandles)
      {
          waitHandle.WaitOne();
      }
      return results;
   }
}

这不会像您自己的尝试那样为管道的每个阶段创建锁定。我省略了,因为它似乎没用。但是,你可以通过包装这样的函数轻松添加它:

var wrappedFunctions = functions.Select(x => AddStageLock(x));

AddStageLock就是这样:

private static Func<T,T> AddStageLock<T>(Func<T,T> function)
{
   object stageLock = new object();
   Func<T, T> wrappedFunction =
      x =>
      {
         lock (stageLock)
         {
            return function(x);
         }
      };
   return wrappedFunction;
}

编辑 Execute实现可能比单线程执行慢,除非为每个单独元素完成的工作使创建等待句柄和安排任务的开销相形见绌在线程池上,要真正受益于多线程,您需要限制开销; .NET 4中的PLINQ通过partitioning the data执行此操作。

答案 2 :(得分:0)

为什么不为每次迭代中断一个线程并将结果聚合在一个锁定资源中。你只需要做。可以使用PLinq。 我想你可能会误解资源的方法。如果它正在处理具有共享资源的关键块,则只需要锁定方法。通过选择关闭资源并从那里进入新线程,您无需管理第二种方法。

I.E。:方法X调用Method1然后将值传递给Method2 arr中的Foreach项目 异步(MethodX(项目));