我正在尝试使用异步方法调用开发方法管道。管道的逻辑如下
我提出了如下代码,但它没有按预期工作,结果永远不会被返回,而且它没有按顺序执行。
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。
答案 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(项目));