จะรอรายการงานแบบอะซิงโครนัสโดยใช้ LINQ ได้อย่างไร?


88

ฉันมีรายการงานที่ฉันสร้างไว้ดังนี้:

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

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

    ...
}

โดยการใช้.ToList()งานควรเริ่มต้นทั้งหมด ตอนนี้ฉันต้องการรอให้เสร็จสิ้นและส่งคืนผลลัพธ์

สิ่งนี้ใช้ได้ใน...บล็อกด้านบน:

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

มันทำในสิ่งที่ฉันต้องการ แต่ดูเหมือนจะค่อนข้างเงอะงะ ฉันอยากจะเขียนอะไรที่ง่ายกว่านี้:

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

... แต่นี่ไม่ได้รวบรวม ฉันขาดอะไรไป? หรือเป็นไปไม่ได้ที่จะแสดงออกด้วยวิธีนี้?


คุณจำเป็นต้องดำเนินการDoSomethingAsync(foo)เป็นลำดับสำหรับแต่ละ foo หรือนี้เป็นผู้สมัครเพื่อParallel.ForEach <Foo> ?
mdisibio

1
@mdisibio - Parallel.ForEachกำลังบล็อก รูปแบบที่นี่มาจากจอนสกีตของAsynchronous C # วิดีโอบน Pluralsight มันดำเนินการแบบขนานโดยไม่มีการปิดกั้น
Matt Johnson-Pint

@mdisibio - นป. พวกเขาวิ่งขนานกัน ลองมัน (นอกจากนี้ดูเหมือนว่าฉันไม่ต้องการ.ToList()ถ้าฉันจะใช้WhenAll)
Matt Johnson-Pint

จุดที่ถ่าย ขึ้นอยู่กับวิธีการDoSomethingAsyncเขียนรายการอาจดำเนินการควบคู่กันหรือไม่ก็ได้ ฉันสามารถเขียนวิธีการทดสอบที่เป็นและเวอร์ชันที่ไม่ใช่ แต่ไม่ว่าในกรณีใดพฤติกรรมจะถูกกำหนดโดยวิธีการนั้นเองไม่ใช่ผู้รับมอบสิทธิ์ที่สร้างงาน ขออภัยสำหรับการผสมผสาน แต่ถ้าDoSomethingAsycผลตอบแทนTask<Foo>แล้วawaitในผู้ร่วมประชุมไม่ได้เป็นสิ่งจำเป็นอย่างยิ่ง ... ผมคิดว่าเป็นจุดหลักที่ผมจะพยายามที่จะทำให้
mdisibio

คำตอบ:


138

LINQ ทำงานกับasyncโค้ดได้ไม่สมบูรณ์แต่คุณสามารถทำได้:

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

หากงานของคุณส่งคืนค่าประเภทเดียวกันทั้งหมดคุณสามารถทำได้:

var results = await Task.WhenAll(tasks);

ซึ่งค่อนข้างดี WhenAllส่งคืนอาร์เรย์ดังนั้นฉันเชื่อว่าวิธีของคุณสามารถส่งคืนผลลัพธ์ได้โดยตรง:

return await Task.WhenAll(tasks);

11
แค่อยากจะชี้ให้เห็นว่าสิ่งนี้สามารถใช้ได้กับvar tasks = foos.Select(foo => DoSomethingAsync(foo)).ToList();
mdisibio

1
หรือแม้กระทั่งvar tasks = foos.Select(DoSomethingAsync).ToList();
Todd Menier

3
อะไรคือเหตุผลเบื้องหลังที่ Linq ไม่ทำงานอย่างสมบูรณ์กับรหัส async?
Ehsan Sajjad

2
@EhsanSajjad: เนื่องจาก LINQ to Objects ทำงานร่วมกับวัตถุในหน่วยความจำ บางสิ่งบางอย่างที่ จำกัด Selectการทำงานเช่น Whereแต่ส่วนใหญ่ทำไม่ได้เช่น
Stephen Cleary

4
@EhsanSajjad: หากการดำเนินการเป็นแบบ I / O คุณสามารถใช้asyncเพื่อลดเธรด ถ้ามันถูกผูกไว้กับ CPU และอยู่บนเธรดพื้นหลังแล้วก็asyncจะไม่ให้ประโยชน์ใด ๆ
Stephen Cleary

9

เพื่อขยายคำตอบของ Stephen ฉันได้สร้างวิธีการขยายต่อไปนี้เพื่อรักษาสไตล์ LINQ ที่คล่องแคล่ว จากนั้นคุณสามารถทำได้

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
โดยส่วนตัวฉันจะตั้งชื่อวิธีการขยายของคุณToArrayAsync
torvin

4

ปัญหาหนึ่งของ Task WhenAll คือมันจะสร้างความเท่าเทียมกัน ในกรณีส่วนใหญ่มันอาจจะดีกว่า แต่บางครั้งคุณก็ต้องการหลีกเลี่ยง ตัวอย่างเช่นการอ่านข้อมูลเป็นกลุ่มจากฐานข้อมูลและการส่งข้อมูลไปยังบริการเว็บระยะไกลบางอย่าง คุณไม่ต้องการโหลดแบทช์ทั้งหมดไปยังหน่วยความจำ แต่กด DB เมื่อชุดก่อนหน้าได้รับการประมวลผล ดังนั้นคุณต้องทำลายความไม่สัมพันธ์กัน นี่คือตัวอย่าง:

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

หมายเหตุ. GetAwaiter (). GetResult () แปลงเป็นซิงโครไนซ์ DB จะถูกตีอย่างขี้เกียจเพียงครั้งเดียวเมื่อมีการประมวลผล batchSize ของเหตุการณ์


1

ใช้Task.WaitAllหรืออย่างTask.WhenAllใดอย่างหนึ่ง


1
ไม่ได้ผลเช่นกัน Task.WaitAllกำลังปิดกั้นไม่สามารถรอได้และจะใช้ไม่ได้กับไฟล์Task<T>.
Matt Johnson-Pint

@MattJohnson WhenAll?
LB

ใช่. แค่นั้นแหละ! ฉันรู้สึกเป็นใบ้ ขอบคุณ!
Matt Johnson-Pint

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.