我有一个集合,其中包含要处理的元素,最多只能处理四个元素。在运行时,所有进程一起启动,所有进程都处于等待状态。一次只处理四个元素。
问题是随机选择处理元素,因为所有线程都在等待资源释放。意味着第一个元素可以是集合中的最后一个元素。
但是,我需要按顺序处理元素。
请告诉我如何实现这一目标?
我正在使用TPL和C#4.0
答案 0 :(得分:5)
对于并行性,总是存在定义“按顺序”意味着什么的问题。假设您有100个项目的集合。按“按顺序”按顺序处理它们(按照您的要求)可能意味着:
排序顺序:使用4个主题并按原始集合的顺序发出任务。
在这种情况下,您可以使用:
ParallelOptions po = new ParallelOptions() { MaxDegreeOfParallelism = 4 };
Parallel.ForEach(list.AsParallel().AsOrdered(), po,
(item) =>
{
// code
});
如果任务不平衡,这将很快失去原有的排序,因为有些线程可能会在繁重的任务上落后,但任务将按顺序分配。
严格排序:按顺序处理它们,如下所示:
0 1 2 3
4 tasks
_____________________________
barrier
4 5 6 7
4 tasks
_____________________________
barrier
etc.
在这种情况下,您可以使用屏障:
Barrier b = new Barrier(4);
ParallelOptions po = new ParallelOptions() { MaxDegreeOfParallelism = 4 };
Parallel.ForEach(list.AsParallel().AsOrdered(), po,
(item) =>
{
// code
b.SignalAndWait();
});
虽然您必须确保任务数量是4的倍数,否则屏障将不会在最后一次迭代时发出信号。
处理单个任务中的4个项目:您可以创建一个封装原始列表的4个项目的任务对象,然后像第一个案例中那样执行简单的Parallel.ForEach
(即每个线程将处理4个项目顺序作为单个任务的一部分)。这将按顺序以4个为一组发出任务,但如果任务花费的时间过长,可能会导致某些线程落后。
答案 1 :(得分:1)
我不清楚你究竟在做什么“随机选择元素”。但是如果你使用Paralle.ForEach()
,那么它会尝试高效,因此它会以某种方式对输入序列进行分区。如果输入序列是IList<T>
,它将使用范围分区,否则,它将使用块分区(参见Chunk partitioning vs range partitioning in PLINQ)。
如果您想按顺序处理项目,可以使用custom partitioner配置Parallel.ForEach()
,这会将集合划分为大小为1的块。
但是由于你在这里并不真正需要Parallel.ForEach()
,可能更简单的解决方案就是创建4个逐个处理项目的任务。要进行同步,您可以使用BlockingCollection
。类似的东西:
public static class ParallelOrdered
{
public static void ForEach<T>(IEnumerable<T> collection, Action<T> action, int degreeOfParallelism)
{
var blockingCollection = new BlockingCollection<T>();
foreach (var item in collection)
blockingCollection.Add(item);
blockingCollection.CompleteAdding();
var tasks = new Task[degreeOfParallelism];
for (int i = 0; i < degreeOfParallelism; i++)
{
tasks[i] = Task.Factory.StartNew(
() =>
{
foreach (var item in blockingCollection.GetConsumingEnumerable())
action(item);
});
}
Task.WaitAll(tasks);
}
}
答案 2 :(得分:1)
这就是我完成这项任务的方式
public delegate void ProcessFinished(IParallelProcess process);
public interface IParallelProcess
{
void Start();
event ProcessFinished ProcessFinished;
}
public class ParallelProcessBasket : ConcurrentQueue<IParallelProcess>
{
public void Put(IParallelProcess process)
{
base.Enqueue(process);
}
public IParallelProcess Get()
{
IParallelProcess process = null;
base.TryDequeue(out process);
return process;
}
}
public class ParallelProcessor<T> where T : class
{
private ParallelProcessBasket basket;
private readonly int MAX_DEGREE_OF_PARALLELISM;
private Action<T> action;
public ParallelProcessor(int degreeOfParallelism, IEnumerable<IParallelProcess> processes, Action<T> action)
{
basket = new ParallelProcessBasket();
this.action = action;
processes.ToList().ForEach(
(p) =>
{
basket.Enqueue(p);
p.ProcessFinished += new ProcessFinished(p_ProcessFinished);
});
MAX_DEGREE_OF_PARALLELISM = degreeOfParallelism;
}
private void p_ProcessFinished(IParallelProcess process)
{
if (!basket.IsEmpty)
{
T element = basket.Get() as T;
if (element != null)
{
Task.Factory.StartNew(() => action(element));
}
}
}
public void StartProcessing()
{
// take first level of iteration
for (int cnt = 0; cnt < MAX_DEGREE_OF_PARALLELISM; cnt++)
{
if (!basket.IsEmpty)
{
T element = basket.Get() as T;
if (element != null)
{
Task.Factory.StartNew(() => action(element));
}
}
}
}
}
static void Main(string[] args)
{
ParallelProcessor<ParallelTask> pr = new ParallelProcessor<ParallelTask>(Environment.ProcessorCount, collection, (e) => e.Method1());
pr.StartProcessing();
}
谢谢..