Parallel.ForEachまたはTask.Run()を使用して一連のタスクを非同期的に開始することの違いは何ですか?
バージョン1:
List<string> strings = new List<string> { "s1", "s2", "s3" };
Parallel.ForEach(strings, s =>
{
DoSomething(s);
});
バージョン2:
List<string> strings = new List<string> { "s1", "s2", "s3" };
List<Task> Tasks = new List<Task>();
foreach (var s in strings)
{
Tasks.Add(Task.Run(() => DoSomething(s)));
}
await Task.WhenAll(Tasks);
この場合、2番目のメソッドは、タスクをブロックする代わりに完了するまで非同期に待機します。
ただし、ループでTask.Run
を使用することには不利な点があります。Parallel.ForEach
を使用すると、 Partitioner
が作成されます。必要以上のタスクを作成しないでください。 Task.Run
は常にアイテムごとに1つのタスクを作成しますが(これを実行しているため)、Parallel
クラスバッチが機能するため、作成するタスクは合計ワークアイテムよりも少なくなります。これにより、特にループ本体のアイテムごとの作業量が少ない場合、全体的なパフォーマンスが大幅に向上します。
この場合、次のように書くことで両方のオプションを組み合わせることができます。
await Task.Run(() => Parallel.ForEach(strings, s =>
{
DoSomething(s);
}));
これは、次の短い形式でも記述できることに注意してください。
await Task.Run(() => Parallel.ForEach(strings, DoSomething));
最初のバージョンは、呼び出しスレッドを同期的にブロックします(そして、そのスレッドでいくつかのタスクを実行します)。
UIスレッドの場合、これによりUIがフリーズします。
2番目のバージョンは、タスクをスレッドプールで非同期的に実行し、完了するまで呼び出しスレッドを解放します。
使用されるスケジューリングアルゴリズムにも違いがあります。
2番目の例は、
await Task.WhenAll(strings.Select(s => Task.Run(() => DoSomething(s)));
Parallel.ForEachが不適切に使用されるのを見てきましたが、この質問の例が役立つと考えました。
コンソールアプリで以下のコードを実行すると、Parallel.ForEachで実行されたタスクが呼び出しスレッドをブロックしないことがわかります。結果(ポジティブまたはネガティブ)を気にしない場合はこれで問題ありませんが、結果が必要な場合は、Task.WhenAllを使用する必要があります。
using System;
using System.Linq;
using System.Threading.Tasks;
namespace ParrellelEachExample
{
class Program
{
static void Main(string[] args)
{
var indexes = new int[] { 1, 2, 3 };
RunExample((prefix) => Parallel.ForEach(indexes, (i) => DoSomethingAsync(i, prefix)),
"Parallel.Foreach");
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("*You'll notice the tasks haven't run yet, because the main thread was not blocked*");
Console.WriteLine("Press any key to start the next example...");
Console.ReadKey();
RunExample((prefix) => Task.WhenAll(indexes.Select(i => DoSomethingAsync(i, prefix)).ToArray()).Wait(),
"Task.WhenAll");
Console.WriteLine("All tasks are done. Press any key to close...");
Console.ReadKey();
}
static void RunExample(Action<string> action, string prefix)
{
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine($"{Environment.NewLine}Starting '{prefix}'...");
action(prefix);
Console.WriteLine($"{Environment.NewLine}Finished '{prefix}'{Environment.NewLine}");
}
static async Task DoSomethingAsync(int i, string prefix)
{
await Task.Delay(i * 1000);
Console.WriteLine($"Finished: {prefix}[{i}]");
}
}
}
結果は次のとおりです。
結論:
Parallel.ForEachをタスクで使用しても、呼び出しスレッドはブロックされません。結果が気になる場合は、必ずタスクを待ってください。
〜乾杯
読みやすいと感じたので、私はこれをしました:
List<Task> x = new List<Task>();
foreach(var s in myCollectionOfObject)
{
// Note there is no await here. Just collection the Tasks
x.Add(s.DoSomethingAsync());
}
await Task.WhenAll(x);