Saya memiliki daftar tugas yang saya buat seperti ini:
public async Task<IList<Foo>> GetFoosAndDoSomethingAsync()
{
var foos = await GetFoosAsync();
var tasks = foos.Select(async foo => await DoSomethingAsync(foo)).ToList();
...
}
Dengan menggunakan .ToList()
, semua tugas harus dimulai. Sekarang saya ingin menunggu penyelesaian mereka dan mengembalikan hasilnya.
Ini berfungsi di ...
blok di atas :
var list = new List<Foo>();
foreach (var task in tasks)
list.Add(await task);
return list;
Itu melakukan apa yang saya inginkan, tetapi ini tampaknya agak kikuk. Saya lebih suka menulis sesuatu yang lebih sederhana seperti ini:
return tasks.Select(async task => await task).ToList();
... tapi ini tidak bisa dikompilasi. Apa yang saya lewatkan? Atau apakah tidak mungkin untuk mengungkapkan hal-hal seperti ini?
c#
linq
async-await
Matt Johnson-Pint
sumber
sumber
DoSomethingAsync(foo)
secara serial untuk setiap foo, atau apakah ini kandidat untuk Parallel.ForEach <Foo> ?Parallel.ForEach
memblokir. Polanya di sini berasal dari video Asynchronous C # Jon Skeet di Pluralsight . Ini dijalankan secara paralel tanpa pemblokiran..ToList()
jika saya hanya akan menggunakanWhenAll
.)DoSomethingAsync
penulisannya, daftar mungkin atau mungkin tidak dijalankan secara paralel. Saya dapat menulis metode pengujian yang dulu dan versi yang tidak, tetapi dalam kedua kasus perilakunya ditentukan oleh metode itu sendiri, bukan delegasi yang membuat tugas. Maaf atas kesalahannya. Namun, jikaDoSomethingAsyc
kembaliTask<Foo>
, makaawait
delegasi tidak mutlak diperlukan ... Saya pikir itu adalah poin utama yang akan saya coba buat.Jawaban:
LINQ tidak berfungsi sempurna dengan
async
kode, tetapi Anda dapat melakukan ini:var tasks = foos.Select(DoSomethingAsync).ToList(); await Task.WhenAll(tasks);
Jika semua tugas Anda mengembalikan jenis nilai yang sama, Anda bahkan dapat melakukan ini:
var results = await Task.WhenAll(tasks);
yang mana cukup bagus.
WhenAll
mengembalikan array, jadi saya yakin metode Anda dapat mengembalikan hasilnya secara langsung:return await Task.WhenAll(tasks);
sumber
var tasks = foos.Select(foo => DoSomethingAsync(foo)).ToList();
var tasks = foos.Select(DoSomethingAsync).ToList();
Select
. Tapi kebanyakan tidak, sukaWhere
.async
untuk mengurangi thread; jika terikat dengan CPU dan sudah ada di thread latar belakang, makaasync
tidak akan memberikan manfaat apa pun.Untuk memperluas jawaban Stephen, saya telah membuat metode ekstensi berikut untuk menjaga gaya LINQ yang lancar . Anda kemudian bisa melakukannya
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); } } }
sumber
ToArrayAsync
Satu masalah dengan Task.WhenAll adalah hal itu akan membuat paralelisme. Dalam kebanyakan kasus mungkin lebih baik, tetapi terkadang Anda ingin menghindarinya. Misalnya, membaca data dalam batch dari DB dan mengirim data ke beberapa layanan web jarak jauh. Anda tidak ingin memuat semua kelompok ke memori tetapi menekan DB setelah kelompok sebelumnya telah diproses. Jadi, Anda harus mematahkan asinkronitas tersebut. Berikut ini contohnya:
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) { }
Catatan .GetAwaiter (). GetResult () mengonversinya menjadi sinkronisasi. DB akan dipukul secara malas hanya setelah batchSize peristiwa diproses.
sumber
Gunakan
Task.WaitAll
atauTask.WhenAll
mana saja yang sesuai.sumber
Task.WaitAll
memblokir, tidak dapat menunggu, dan tidak akan berfungsi dengan fileTask<T>
.WhenAll
?Task.WhenAll harus melakukan trik di sini.
sumber