LINQ kullanarak eşzamansız olarak bir görev listesi nasıl beklenir?


88

Şu şekilde oluşturduğum görevlerin bir listesi var:

public async Task<IList<Foo>> GetFoosAndDoSomethingAsync()
{
    var foos = await GetFoosAsync();

    var tasks = foos.Select(async foo => await DoSomethingAsync(foo)).ToList();

    ...
}

Kullanarak .ToList(), görevlerin tümü başlamalıdır. Şimdi tamamlanmalarını beklemek ve sonuçları geri vermek istiyorum.

Bu, yukarıdaki ...blokta çalışır :

var list = new List<Foo>();
foreach (var task in tasks)
    list.Add(await task);
return list;

İstediğimi yapıyor, ama bu oldukça beceriksiz görünüyor. Bunun gibi daha basit bir şey yazmayı tercih ederim:

return tasks.Select(async task => await task).ToList();

... ama bu derlemez. Neyi kaçırıyorum? Yoksa olayları bu şekilde ifade etmek mümkün değil mi?


DoSomethingAsync(foo)Her foo için seri olarak işlem yapmanız gerekiyor mu , yoksa bu Parallel.ForEach <Foo> için bir aday mı?
mdisibio

1
@mdisibio - Parallel.ForEachengelliyor. Buradaki kalıp , Pluralsight'taki Jon Skeet'in Asenkron C # videosundan geliyor . Engellemeden paralel olarak çalışır.
Matt Johnson-Pint

@mdisibio - Hayır. Paralel olarak çalışırlar. Deneyin . (Ek olarak, .ToList()sadece kullanacaksam ihtiyacım yok gibi görünüyor WhenAll.)
Matt Johnson-Pint 19'14

Alınan nokta. Nasıl DoSomethingAsyncyazıldığına bağlı olarak, liste paralel olarak yürütülebilir veya yürütülemeyebilir. Eski ve olmayan bir sürüm test yöntemi yazabildim, ancak her iki durumda da davranış, görevi oluşturan temsilci tarafından değil, yöntemin kendisi tarafından belirlenir. Karışıklık için özür dilerim. Bununla birlikte, DoSomethingAsycgeri dönerse Task<Foo>, o zaman awaitdelege kesinlikle gerekli değildir ... Sanırım yapmaya çalışacağım ana nokta buydu.
mdisibio

Yanıtlar:


138

LINQ, asynckodla mükemmel şekilde çalışmaz , ancak bunu yapabilirsiniz:

var tasks = foos.Select(DoSomethingAsync).ToList();
await Task.WhenAll(tasks);

Görevlerinizin tümü aynı türde bir değer döndürüyorsa, bunu bile yapabilirsiniz:

var results = await Task.WhenAll(tasks);

bu oldukça güzel. WhenAllbir dizi döndürür, bu yüzden yönteminizin sonuçları doğrudan döndürebileceğine inanıyorum:

return await Task.WhenAll(tasks);

11
Sadece bununla da çalışabileceğini belirtmek istedimvar tasks = foos.Select(foo => DoSomethingAsync(foo)).ToList();
mdisibio

1
hattavar tasks = foos.Select(DoSomethingAsync).ToList();
Todd Menier

3
Linq'in eşzamansız kod ile mükemmel çalışmamasının arkasındaki sebep nedir?
Ehsan Sajjad

2
@EhsanSajjad: Çünkü LINQ to Objects, bellek içi nesneler üzerinde eşzamanlı olarak çalışır. Bazı sınırlı şeyler çalışır, örneğin Select. Ama çoğu sevmiyor Where.
Stephen Cleary

4
@EhsanSajjad: İşlem G / Ç tabanlıysa, asynckonuları azaltmak için kullanabilirsiniz ; CPU'ya bağlıysa ve zaten bir arka planda iş parçacığı üzerindeyse asyncherhangi bir fayda sağlamaz.
Stephen Cleary

9

Stephen'ın cevabını genişletmek için, LINQ'nun akıcı stilini korumak için aşağıdaki uzantı yöntemini oluşturdum . O zaman yapabilirsin

await someTasks.WhenAll()

namespace System.Linq
{
    public static class IEnumerableExtensions
    {
        public static Task<T[]> WhenAll<T>(this IEnumerable<Task<T>> source)
        {
            return Task.WhenAll(source);
        }
    }
}

10
Şahsen, uzatma yönteminize isim ToArrayAsync
verirdim

4

Task.WhenAll ile ilgili bir sorun, bir paralellik yaratmasıdır. Çoğu durumda daha da iyi olabilir, ancak bazen bundan kaçınmak istersiniz. Örneğin, verileri veri tabanından gruplar halinde okumak ve bazı uzak web hizmetlerine veri göndermek. Tüm partileri belleğe yüklemek istemezsiniz, ancak önceki parti işlendikten sonra DB'ye vurursunuz. Yani, eşzamansızlığı kırmanız gerekiyor. İşte bir örnek:

var events = Enumerable.Range(0, totalCount/ batchSize)
   .Select(x => x*batchSize)
   .Select(x => dbRepository.GetEventsBatch(x, batchSize).GetAwaiter().GetResult())
   .SelectMany(x => x);
foreach (var carEvent in events)
{
}

Not .GetAwaiter (). GetResult () onu senkronizasyona dönüştürür. DB yalnızca olayların batchSize işlemi işlendikten sonra tembel bir şekilde vurulur.



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.