次のように並列ループを使用して何かを処理したいです:
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();
指示を書いた場所に
ベストアンサー1
短いバージョン - いいえ、イテレータ ブロック経由では不可能です。長いバージョンでは、おそらく、呼び出し元のイテレータ スレッド (デキューを実行) と並列ワーカー (エンキューを実行) 間の同期されたキュー/デキューが必要になります。ただし、補足として、ログは通常 IO バウンドであり、IO バウンドのものを並列化してもうまく機能しないことがよくあります。
発信者が時間をかけ消費するそれぞれの場合、一度に1つのログのみを処理するアプローチにはメリットがあるかもしれませんが、その間呼び出し側は前のログを消費しています。つまり、始まる1つの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();
}