我想使用这样的并行循环处理一些东西:
public void FillLogs(IEnumerable<IComputer> computers)
{
Parallel.ForEach(computers, cpt=>
{
cpt.Logs = cpt.GetRawLogs().ToList();
});
}
好的,它运作正常。但是,如果我希望FillLogs方法返回IEnumerable,怎么办?
public IEnumerable<IComputer> FillLogs(IEnumerable<IComputer> computers)
{
Parallel.ForEach(computers, cpt=>
{
cpt.Logs = cpt.GetRawLogs().ToList();
yield return cpt // KO, don't work
});
}
修改
似乎不可能......但我使用的是这样的东西:
public IEnumerable<IComputer> FillLogs(IEnumerable<IComputer> computers)
{
return computers.AsParallel().Select(cpt => cpt);
}
但我放了cpt.Logs = cpt.GetRawLogs().ToList();
指令
答案 0 :(得分:13)
短版本 - 不,通过迭代器块无法实现;较长的版本可能涉及调用者的迭代器线程(执行dequeue)和并行工作者(执行enqueue)之间的同步队列/出队;但是作为旁注 - 日志通常是IO绑定的,并且对IO绑定的东西进行并行化通常不会很好。
如果调用者需要花费一些时间来消费,那么对于一次只处理一个日志的方法可能有一些优点,但可以做到而< / strong>调用者正在使用以前的日志;即{strong>开始 Task
yield
之前的下一个项目,等待yield
之后的完成...但是这又是非常复杂的。作为简化示例:
static void Main()
{
foreach(string s in Get())
{
Console.WriteLine(s);
}
}
static IEnumerable<string> Get() {
var source = new[] {1, 2, 3, 4, 5};
Task<string> outstandingItem = null;
Func<object, string> transform = x => ProcessItem((int) x);
foreach(var item in source)
{
var tmp = outstandingItem;
// note: passed in as "state", not captured, so not a foreach/capture bug
outstandingItem = new Task<string>(transform, item);
outstandingItem.Start();
if (tmp != null) yield return tmp.Result;
}
if (outstandingItem != null) yield return outstandingItem.Result;
}
static string ProcessItem(int i)
{
return i.ToString();
}
答案 1 :(得分:3)
我不想冒犯,但也许缺乏理解。 Parallel.ForEach
表示TPL将根据多个线程中的可用硬件运行foreach。但这意味着,ii可以并行完成这项工作! yield return
让您有机会从列表中获取一些值(或者更新),并在需要时逐个返回。它可以防止首先找到符合条件的所有项目,然后迭代它们。这确实是一种性能优势,但不能并行完成。
答案 2 :(得分:0)
怎么样
Queue<string> qu = new Queue<string>();
bool finished = false;
Task.Factory.StartNew(() =>
{
Parallel.ForEach(get_list(), (item) =>
{
string itemToReturn = heavyWorkOnItem(item);
lock (qu)
qu.Enqueue(itemToReturn );
});
finished = true;
});
while (!finished)
{
lock (qu)
while (qu.Count > 0)
yield return qu.Dequeue();
//maybe a thread sleep here?
}
修改强> 我认为这更好:
public static IEnumerable<TOutput> ParallelYieldReturn<TSource, TOutput>(this IEnumerable<TSource> source, Func<TSource, TOutput> func)
{
ConcurrentQueue<TOutput> qu = new ConcurrentQueue<TOutput>();
bool finished = false;
AutoResetEvent re = new AutoResetEvent(false);
Task.Factory.StartNew(() =>
{
Parallel.ForEach(source, (item) =>
{
qu.Enqueue(func(item));
re.Set();
});
finished = true;
re.Set();
});
while (!finished)
{
re.WaitOne();
while (qu.Count > 0)
{
TOutput res;
if (qu.TryDequeue(out res))
yield return res;
}
}
}
编辑2:我同意简短的否回答。这段代码没用;你无法打破收益率循环。
答案 3 :(得分:0)
尽管这个问题很老,但我还是设法做些有趣的事情。
class Program
{
static void Main(string[] args)
{
foreach (var message in GetMessages())
{
Console.WriteLine(message);
}
}
// Parallel yield
private static IEnumerable<string> GetMessages()
{
int total = 0;
bool completed = false;
var batches = Enumerable.Range(1, 100).Select(i => new Computer() { Id = i });
var qu = new ConcurrentQueue<Computer>();
Task.Run(() =>
{
try
{
Parallel.ForEach(batches,
() => 0,
(item, loop, subtotal) =>
{
Thread.Sleep(1000);
qu.Enqueue(item);
return subtotal + 1;
},
result => Interlocked.Add(ref total, result));
}
finally
{
completed = true;
}
});
int current = 0;
while (current < total || !completed)
{
SpinWait.SpinUntil(() => current < total || completed);
if (current == total) yield break;
current++;
qu.TryDequeue(out Computer computer);
yield return $"Completed {computer.Id}";
}
}
}
public class Computer
{
public int Id { get; set; }
}
与Koray的回答相比,这确实使用了所有CPU内核。
答案 4 :(得分:0)
您可以使用以下扩展方法
public static class ParallelExtensions
{
public static IEnumerable<T1> OrderedParallel<T, T1>(this IEnumerable<T> list, Func<T, T1> action)
{
var unorderedResult = new ConcurrentBag<(long, T1)>();
Parallel.ForEach(list, (o, state, i) =>
{
unorderedResult.Add((i, action.Invoke(o)));
});
var ordered = unorderedResult.OrderBy(o => o.Item1);
return ordered.Select(o => o.Item2);
}
}
使用方式:
public void FillLogs(IEnumerable<IComputer> computers)
{
cpt.Logs = computers.OrderedParallel(o => o.GetRawLogs()).ToList();
}
希望这可以为您节省一些时间。