ForEach ile Async'i nasıl kullanabilirim?


123

ForEach kullanırken Async kullanmak mümkün müdür? Aşağıda denediğim kod:

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

Şu hatayı alıyorum:

'Async' adı mevcut bağlamda mevcut değil

Using ifadesinin içine alındığı yöntem zaman uyumsuz olarak ayarlanır.

Yanıtlar:


180

List<T>.ForEachasync(aynı nedenlerden ötürü LINQ-nesneler arasında da iyi oynamıyor ).

Bu durumda, her öğeyi eşzamansız bir işleme yansıtmanızı öneririm ve daha sonra (eşzamansız olarak) hepsinin tamamlanmasını bekleyebilirsiniz.

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

Bu yaklaşımın bir asyncdelege vermeye kıyasla faydaları ForEachşunlardır:

  1. Hata işleme daha uygundur. İle ilgili istisnalar async voidyakalanamaz catch; bu yaklaşım, await Task.WhenAllhattaki istisnaları yayacak ve doğal istisna işlemeye izin verecektir .
  2. Bu yöntemin sonunda görevlerin tamamlandığını biliyorsunuz, çünkü bir await Task.WhenAll. Eğer kullanırsanasync void işlemler tamamladıktan sonra, kolayca söyleyemem.
  3. Bu yaklaşım, sonuçları almak için doğal bir sözdizimine sahiptir. GetAdminsFromGroupAsyncbir sonuç (yöneticiler) üreten bir işlem gibi görünüyor ve bu tür işlemler yan etki olarak bir değer ayarlamak yerine sonuçlarını döndürebiliyorsa daha doğaldır .

5
Hiçbir şeyi değiştirmediğinden değil, ancak List.ForEach()LINQ'nun bir parçası değil.
svick

Harika bir öneri @StephenCleary ve verdiğiniz tüm cevaplar için teşekkür ederim async. Çok yardımcı oldular!
Justin Helgerson

4
@StewartAnderson: Görevler eşzamanlı olarak yürütülecektir. Seri yürütme için bir uzantı yoktur; Sadece bir yapmak foreachbir ile awaitsizin döngü gövdesindeki.
Stephen Cleary

1
@mare: ForEachyalnızca eşzamanlı bir temsilci türü alır ve eşzamansız bir temsilci türü alarak aşırı yükleme yoktur. Yani kısa cevap "kimse asenkron yazmadı ForEach". Daha uzun yanıt, bazı anlambilimlerin varsayılması gerektiğidir; Örneğin, öğeler teker teker mi foreach(benzer Select) yoksa eşzamanlı mı (benzer ) işlenmeli ? Her seferinde bir tane olsaydı, eşzamansız akışlar daha iyi bir çözüm olmaz mıydı? Aynı anda ise, sonuçlar orijinal madde sırasına mı yoksa tamamlanma sırasına göre mi olmalıdır? İlk başarısızlıkta başarısız mı olmalı yoksa tümü tamamlanana kadar mı beklemeli? Etc.
Stephen Cleary

2
@RogerWolf: Evet; SemaphoreSlimasenkron görevleri kısmak için kullanın .
Stephen Cleary

61

Bu küçük uzantı yöntemi size istisnasız eşzamansız yineleme sağlamalıdır:

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

Lambda'nın dönüş türünü olarak olarak değiştirdiğimiz voidiçin Task, istisnalar doğru şekilde yayılacaktır. Bu, pratikte böyle bir şey yazmanıza izin verecektir:

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

Sanırım asyncdaha önce olmalıi =>
Todd

ForEachAsyn () 'i beklemek yerine, bir Wait () çağırabilir.
Jonas

Lambda'nın burada beklenmesine gerek yok.
hazzik

Buna, Todd'un
Yanıtı'nda

ForEachAsyncBekleyen muhtemelen ile yapılandırılmalıdır böylece, esasen bir kütüphane yöntemdir ConfigureAwait(false).
Theodor Zoulias

9

Basit cevap, yöntemi foreachyerine anahtar kelimeyi kullanmaktır .ForEach()List()

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

Sen bir dahisin
Vick_onrails

8

Sıralı işleme ile yukarıdaki eşzamansız foreach varyantlarının gerçek çalışan bir versiyonu:

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);
}

İşte uygulama:

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();
}

Temel fark nedir? .ConfigureAwait(false);Bu, her görevin eşzamansız sıralı işlenmesi sırasında ana iş parçacığının bağlamını korur.


6

Başlangıç ​​olarak C# 8.0, akışları eşzamansız olarak oluşturabilir ve tüketebilirsiniz.

    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;
        }
    }

Daha


1
Bu, her bir öğeyi beklemenin yanı sıra MoveNext, artık numaralandırıcıyı da beklemeniz avantajına sahiptir . Bu, numaralandırıcının bir sonraki öğeyi anında getiremediği ve birinin kullanılabilir olmasını beklemesi gerektiği durumlarda önemlidir.
Theodor Zoulias

3

Bu uzantı yöntemini ekleyin

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);
            }));
    }
}

Ve sonra şu şekilde kullanın:

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

Sorun, asyncanahtar kelimenin gövdeden önce değil lambdadan önce görünmesi gerektiğiydi:

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

35
Gereksiz ve ince kullanım için -1 async void. Bu yaklaşımın, istisna işleme ve zaman uyumsuz işlemlerin ne zaman tamamlandığını bilmeyle ilgili sorunları vardır.
Stephen Cleary

Evet, bunun istisnaları gerektiği gibi ele almadığını fark ettim.
Herman Schoenfeld
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.