ForEachで非同期を使用するにはどうすればよいですか?


123

ForEachを使用するときに非同期を使用することは可能ですか?以下は私が試しているコードです:

using (DataContext db = new DataLayer.DataContext())
{
    db.Groups.ToList().ForEach(i => async {
        await GetAdminsFromGroup(i.Gid);
    });
}

エラーが発生します:

「非同期」という名前は現在のコンテキストに存在しません

usingステートメントが囲まれているメソッドは、非同期に設定されます。

回答:


180

List<T>.ForEachasync(同じ理由でLINQ-to-objects でもうまく機能しません)。

この場合、各要素を非同期操作に投影することをお勧めします。その後、それらが(非同期に)すべて完了するまで待機できます。

using (DataContext db = new DataLayer.DataContext())
{
    var tasks = db.Groups.ToList().Select(i => GetAdminsFromGroupAsync(i.Gid));
    var results = await Task.WhenAll(tasks);
}

asyncデリゲートを与えることに対するこのアプローチの利点ForEachは次のとおりです。

  1. エラー処理はより適切です。からの例外async voidはでキャッチできませんcatch。このアプローチは、例外をawait Task.WhenAllラインで伝播し、自然な例外処理を可能にします。
  2. タスクを完了するのは、このメソッドの最後ですawait Task.WhenAll。を使用するasync void場合、操作がいつ完了したかは簡単にはわかりません。
  3. このアプローチには、結果を取得するための自然な構文があります。GetAdminsFromGroupAsync結果を生成する操作(管理者)のように聞こえますが、そのようなコードが副作用として値を設定するのではなく、結果を返すことができる場合、そのようなコードはより自然です。

5
何かを変更するわけでList.ForEach()はありませんが、LINQの一部ではありません。
2013

素晴らしい提案@StephenCleary、そしてあなたがこれまでに出してくれたすべての答えに感謝しますasync。彼らはとても役に立ちました!
Justin Helgerson、2014

4
@StewartAnderson:タスクは同時に実行されます。シリアル実行の拡張はありません。ただやるforeachawait自分のループ本体インチ
Stephen Cleary

1
@mare:ForEach同期デリゲート型のみを受け取り、非同期デリゲート型を取るオーバーロードはありません。つまり、短い答えは「非同期を書いた人は誰もいないForEach」ということです。より長い答えは、いくつかのセマンティクスを想定する必要があるということです。たとえば、アイテムは一度に1つずつ(などforeach)、または同時に(などSelect)処理する必要がありますか?一度に1つである場合、非同期ストリームはより良いソリューションではないでしょうか?同時に行う場合、結果は元のアイテムの順序である必要がありますか、それとも完了順になりますか?最初の失敗で失敗するか、すべてが完了するまで待つべきですか?その他
Stephen Cleary

2
@RogerWolf:はい。SemaphoreSlim非同期タスクを抑制するために使用します。
Stephen Cleary

61

この小さな拡張メソッドは、例外に対して安全な非同期反復を提供します。

public static async Task ForEachAsync<T>(this List<T> list, Func<T, Task> func)
{
    foreach (var value in list)
    {
        await func(value);
    }
}

ラムダの戻り値の型をからvoidに変更しているためTask、例外は正しく伝播されます。これにより、実際に次のようなものを書くことができます。

await db.Groups.ToList().ForEachAsync(async i => {
    await GetAdminsFromGroup(i.Gid);
});

私はasync前にあるべきだと信じていますi =>
トッド

ForEachAsyn()を待つ代わりに、Wait()を呼び出すこともできます。
ジョナス

ラムダはここで待つ必要はありません。
hazzik 2017

私はここでトッドの答えのようにそのにCancellationTokenのサポートを追加しますstackoverflow.com/questions/29787098/...
Zorkind

ForEachAsync待っているが、おそらくで設定する必要がありますので、基本的に図書館法ですConfigureAwait(false)
Theodor Zoulias

9

単純な答えはforeach、のForEach()メソッドの代わりにキーワードを使用することですList()

using (DataContext db = new DataLayer.DataContext())
{
    foreach(var i in db.Groups)
    {
        await GetAdminsFromGroup(i.Gid);
    }
}

あなたは天才です
Vick_onrails

8

以下は、順次処理を行う上記の非同期foreachバリアントの実際の作業バージョンです。

public static async Task ForEachAsync<T>(this List<T> enumerable, Action<T> action)
{
    foreach (var item in enumerable)
        await Task.Run(() => { action(item); }).ConfigureAwait(false);
}

ここに実装があります:

public async void SequentialAsync()
{
    var list = new List<Action>();

    Action action1 = () => {
        //do stuff 1
    };

    Action action2 = () => {
        //do stuff 2
    };

    list.Add(action1);
    list.Add(action2);

    await list.ForEachAsync();
}

主な違いは何ですか?.ConfigureAwait(false);これは、各タスクの非同期順次処理中にメインスレッドのコンテキストを保持します。


6

以降、C# 8.0ストリームを非同期で作成および使用できます。

    private async void button1_Click(object sender, EventArgs e)
    {
        IAsyncEnumerable<int> enumerable = GenerateSequence();

        await foreach (var i in enumerable)
        {
            Debug.WriteLine(i);
        }
    }

    public static async IAsyncEnumerable<int> GenerateSequence()
    {
        for (int i = 0; i < 20; i++)
        {
            await Task.Delay(100);
            yield return i;
        }
    }

もっと


1
これには、各要素を待機するだけMoveNextでなく、列挙子のも待機するという利点があります。これは、列挙子がすぐに次の要素をフェッチできず、使用可能になるまで待つ必要がある場合に重要です。
Theodor Zoulias

3

この拡張メソッドを追加

public static class ForEachAsyncExtension
{
    public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
    {
        return Task.WhenAll(from partition in Partitioner.Create(source).GetPartitions(dop) 
            select Task.Run(async delegate
            {
                using (partition)
                    while (partition.MoveNext())
                        await body(partition.Current).ConfigureAwait(false);
            }));
    }
}

そして、そのように使用します:

Task.Run(async () =>
{
    var s3 = new AmazonS3Client(Config.Instance.Aws.Credentials, Config.Instance.Aws.RegionEndpoint);
    var buckets = await s3.ListBucketsAsync();

    foreach (var s3Bucket in buckets.Buckets)
    {
        if (s3Bucket.BucketName.StartsWith("mybucket-"))
        {
            log.Information("Bucket => {BucketName}", s3Bucket.BucketName);

            ListObjectsResponse objects;
            try
            {
                objects = await s3.ListObjectsAsync(s3Bucket.BucketName);
            }
            catch
            {
                log.Error("Error getting objects. Bucket => {BucketName}", s3Bucket.BucketName);
                continue;
            }

            // ForEachAsync (4 is how many tasks you want to run in parallel)
            await objects.S3Objects.ForEachAsync(4, async s3Object =>
            {
                try
                {
                    log.Information("Bucket => {BucketName} => {Key}", s3Bucket.BucketName, s3Object.Key);
                    await s3.DeleteObjectAsync(s3Bucket.BucketName, s3Object.Key);
                }
                catch
                {
                    log.Error("Error deleting bucket {BucketName} object {Key}", s3Bucket.BucketName, s3Object.Key);
                }
            });

            try
            {
                await s3.DeleteBucketAsync(s3Bucket.BucketName);
            }
            catch
            {
                log.Error("Error deleting bucket {BucketName}", s3Bucket.BucketName);
            }
        }
    }
}).Wait();

2

問題は、asyncキーワードが本文の前ではなく、ラムダの前に表示される必要があることでした。

db.Groups.ToList().ForEach(async (i) => {
    await GetAdminsFromGroup(i.Gid);
});

35
-1の不要で微妙な使用async void。このアプローチには、例外処理に関する問題と、非同期操作がいつ完了するかを知る問題があります。
Stephen Cleary 2013

はい、これは例外を適切に処理しないことがわかりました。
Herman Schoenfeld 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.