ใช้งาน async หลายงานและรอให้การดำเนินการทั้งหมดเสร็จสมบูรณ์


265

ฉันต้องเรียกใช้งาน async หลายรายการในแอปพลิเคชันคอนโซลและรอให้งานทั้งหมดดำเนินการจนเสร็จสิ้น

มีบทความมากมายที่นั่น แต่ฉันดูเหมือนจะสับสนมากยิ่งฉันอ่าน ฉันได้อ่านและทำความเข้าใจหลักการพื้นฐานของ Task Library แล้ว แต่ฉันขาดการเชื่อมโยงอย่างชัดเจน

ฉันเข้าใจว่าเป็นไปได้ที่จะเชื่อมโยงงานเพื่อให้พวกเขาเริ่มต้นหลังจากเสร็จสมบูรณ์ (ซึ่งเป็นสถานการณ์ที่ค่อนข้างมากสำหรับบทความทั้งหมดที่ฉันได้อ่าน) แต่ฉันต้องการให้งานทั้งหมดของฉันทำงานพร้อมกันและฉันอยากรู้อีกครั้ง เสร็จสิ้นทั้งหมด

การใช้งานที่ง่ายที่สุดสำหรับสถานการณ์เช่นนี้คืออะไร

คำตอบ:


440

คำตอบทั้งสองไม่ได้กล่าวถึงสิ่งที่รอคอยได้Task.WhenAll:

var task1 = DoWorkAsync();
var task2 = DoMoreWorkAsync();

await Task.WhenAll(task1, task2);

ความแตกต่างที่สำคัญระหว่างTask.WaitAllและTask.WhenAllคือการที่อดีตจะปิดกั้น (คล้ายกับการใช้Waitในงานเดียว) ในขณะที่คนหลังจะไม่และสามารถรอได้ให้การควบคุมกลับไปที่ผู้โทรจนกว่างานทั้งหมดจะเสร็จสิ้น

ยิ่งกว่านั้นการจัดการข้อยกเว้นต่างกัน:

Task.WaitAll:

อย่างน้อยหนึ่งในอินสแตนซ์ของงานถูกยกเลิกหรือมีข้อผิดพลาดเกิดขึ้นระหว่างการดำเนินการของอินสแตนซ์ของงานอย่างน้อยหนึ่งรายการ หากงานถูกยกเลิก AggregateException มี OperationCanceledException ในคอลเล็กชัน InnerExceptions

Task.WhenAll:

หากงานใด ๆ ที่จัดให้เสร็จสมบูรณ์ในสถานะที่มีความผิดพลาดงานที่ส่งคืนจะเสร็จสมบูรณ์ในสถานะ Faulted โดยที่ข้อยกเว้นจะมีการรวมชุดของข้อยกเว้นที่ยังไม่ได้เปิดจากแต่ละงานที่ให้มา

หากไม่มีงานใดที่ให้มาผิด แต่อย่างน้อยหนึ่งงานถูกยกเลิกงานที่ส่งคืนจะสิ้นสุดในสถานะยกเลิก

หากไม่มีงานใด ๆ ที่ผิดพลาดและไม่มีงานใดถูกยกเลิกงานที่เป็นผลลัพธ์จะสิ้นสุดในสถานะ RanToCompletion หากอาร์เรย์ที่ระบุ / นับไม่ได้งานที่ส่งคืนจะเปลี่ยนเป็นสถานะ RanToCompletion ทันทีก่อนที่จะถูกส่งคืนไปยังผู้โทร


4
เมื่อฉันลองทำสิ่งนี้งานของฉันจะทำงานตามลำดับหรือไม่ เราต้องเริ่มงานแต่ละงานทีละคนมาก่อนawait Task.WhenAll(task1, task2);หรือไม่?
Zapnologica

4
@Zapnologica Task.WhenAllไม่เริ่มงานให้คุณ คุณต้องให้พวกเขา "ร้อน" ความหมายเริ่มขึ้นแล้ว
Yuval Itzchakov

2
ตกลง. นั่นทำให้รู้สึก ตัวอย่างของคุณจะทำอย่างไร เพราะคุณยังไม่ได้เริ่ม?
Zapnologica

2
@YuvalItzchakov ขอบคุณมาก! มันง่ายมาก แต่มันช่วยฉันได้มากในวันนี้! มีค่าอย่างน้อย 1,000 รายการ :)
Daniel Dušek

1
@ ก่อนหน้าฉันไม่ได้ติดตาม อะไรStartNewและการปั่นงานใหม่จะต้องทำด้วยแบบไม่พร้อมรอให้พวกเขาทั้งหมดหรือไม่
Yuval Itzchakov

106

คุณสามารถสร้างงานได้หลายอย่างเช่น:

List<Task> TaskList = new List<Task>();
foreach(...)
{
   var LastTask = new Task(SomeFunction);
   LastTask.Start();
   TaskList.Add(LastTask);
}

Task.WaitAll(TaskList.ToArray());

48
ฉันจะแนะนำ WhenAll
Ravi

เป็นไปได้ไหมที่จะเริ่มกระทู้ใหม่หลาย ๆ อันในเวลาเดียวกันโดยใช้คีย์เวิร์ด await แทน. Start ()
Matt W

1
@MattW ไม่เมื่อคุณใช้รอมันจะรอให้เสร็จสมบูรณ์ ในกรณีนี้คุณจะไม่สามารถสร้างสภาพแวดล้อมแบบมัลติเธรด นี่คือเหตุผลที่ภารกิจทั้งหมดรออยู่เมื่อสิ้นสุดลูป
ไวรัส

5
Downvote สำหรับผู้อ่านในอนาคตเนื่องจากไม่ชัดเจนว่านี่เป็นการปิดกั้นการโทร
JRoughan

ดูคำตอบที่ยอมรับได้ด้วยเหตุผลว่าทำไมไม่ทำเช่นนี้
EL MOJO

26

ตัวเลือกที่ดีที่สุดที่ฉันเคยเห็นคือวิธีการต่อไปนี้:

public static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) {
    return Task.WhenAll(sequence.Select(action));
}

เรียกว่าเป็นแบบนี้:

await sequence.ForEachAsync(item => item.SomethingAsync(blah));

หรือด้วยแลมบ์ดา async:

await sequence.ForEachAsync(async item => {
    var more = await GetMoreAsync(item);
    await more.FrobbleAsync();
});

26

คุณสามารถใช้WhenAllสิ่งที่จะคืนค่าที่ไม่สามารถรอได้TaskหรือWaitAllไม่มีประเภทการคืนค่าและจะบล็อกการใช้งานโค้ดเพิ่มเติมเพื่อThread.Sleepจนกว่างานทั้งหมดจะเสร็จสมบูรณ์ยกเลิกหรือผิดพลาด

ป้อนคำอธิบายรูปภาพที่นี่

ตัวอย่าง

var tasks = new Task[] {
    TaskOperationOne(),
    TaskOperationTwo()
};

Task.WaitAll(tasks);
// or
await Task.WhenAll(tasks);

หากคุณต้องการที่จะเรียกใช้งานในลำดับ praticular คุณจะได้รับแรงบันดาลใจในรูปแบบนี้ Anwser


ขอโทษที่มาของบุคคลที่ปลาย แต่ทำไมคุณมีawaitสำหรับการดำเนินงานในแต่ละครั้งและในการใช้งานครั้งเดียวหรือWaitAll WhenAllไม่ควรทำงานในการTask[]เริ่มต้นawaitหรือไม่?
dee zg

@dee zg คุณพูดถูก การรอคอยเหนือเอาชนะจุดประสงค์ ฉันจะเปลี่ยนคำตอบและลบออก
NtFreX

ใช่นั่นแหละ ขอบคุณสำหรับการชี้แจง! (โหวตขึ้นเพื่อคำตอบที่ดี)
dee zg

8

คุณต้องการที่จะเชื่อมโยงTasks หรือไม่พวกเขาสามารถเรียกในลักษณะคู่ขนาน?

สำหรับการผูกมัด
เพียงทำบางสิ่งเช่น

Task.Run(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);
Task.Factory.StartNew(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);

และอย่าลืมตรวจสอบTaskอินสแตนซ์ก่อนหน้าในแต่ละอันContinueWithเนื่องจากอาจผิดพลาด

สำหรับวิธีขนานวิธี
ที่ง่ายที่สุดที่ฉันเจอ: Parallel.Invoke ไม่เช่นนั้นจะมีTask.WaitAllหรือคุณสามารถใช้WaitHandles เพื่อนับถอยหลังเป็นศูนย์การกระทำที่เหลือ (รอมีคลาสใหม่:) CountdownEventหรือ ...


3
ขอบคุณคำตอบ แต่คำแนะนำของคุณอาจได้รับการอธิบายเพิ่มเติมอีกเล็กน้อย
Daniel Minnaar

@drminnaar คำอธิบายอื่นใดนอกเหนือจากลิงค์ไปยัง msdn พร้อมด้วยตัวอย่างที่คุณต้องการ? คุณไม่ได้คลิกที่ลิงค์ใช่มั้ย
Andreas Niedermair

4
ฉันคลิกที่ลิงก์และฉันอ่านเนื้อหา ฉันกำลังจะเรียกใช้ Invoke แต่มีจำนวนมากถ้าและและเกี่ยวกับว่ามันทำงานไม่พร้อมกันหรือไม่ คุณกำลังแก้ไขคำตอบของคุณอย่างต่อเนื่อง ลิงก์ WaitAll ที่คุณโพสต์นั้นสมบูรณ์แบบ แต่ฉันไปเพื่อคำตอบที่แสดงให้เห็นถึงฟังก์ชั่นเดียวกันในวิธีที่รวดเร็วและง่ายต่อการอ่าน คำตอบของคุณยังคงเป็นทางเลือกที่ดีสำหรับแนวทางอื่น ๆ
Daniel Minnaar

@drminnaar ไม่มีความผิดที่นี่ฉันแค่อยากรู้ :)
Andreas Niedermair

5

นี่คือวิธีที่ฉันทำกับอาร์เรย์Func <> :

var tasks = new Func<Task>[]
{
   () => myAsyncWork1(),
   () => myAsyncWork2(),
   () => myAsyncWork3()
};

await Task.WhenAll(tasks.Select(task => task()).ToArray()); //Async    
Task.WaitAll(tasks.Select(task => task()).ToArray()); //Or use WaitAll for Sync

1
ทำไมคุณไม่เก็บไว้เป็น Task Array?
Talha Talip Açıkgöz

1
หากคุณไม่ระวัง @ talha-talip-açıkgözคุณจะทำงาน Tasks เมื่อคุณไม่คาดหวังให้ทำงาน การทำมันในฐานะตัวแทน Func ทำให้ความตั้งใจของคุณชัดเจน
DalSoft

5

ยังมีคำตอบอื่น ... แต่ฉันมักจะพบว่าตัวเองเป็นกรณีเมื่อฉันต้องการโหลดข้อมูลพร้อมกันและใส่ลงในตัวแปรเช่น:

var cats = new List<Cat>();
var dog = new Dog();

var loadDataTasks = new Task[]
{
    Task.Run(async () => cats = await LoadCatsAsync()),
    Task.Run(async () => dog = await LoadDogAsync())
};

try
{
    await Task.WhenAll(loadDataTasks);
}
catch (Exception ex)
{
    // handle exception
}

1
หากLoadCatsAsync()และLoadDogAsync()เป็นเพียงการเรียกฐานข้อมูลพวกเขาจะถูกผูกไว้ IO Task.Run()ใช้สำหรับงานที่ใช้ CPU มันเพิ่มค่าใช้จ่ายที่ไม่จำเป็นเพิ่มเติมหากสิ่งที่คุณทำกำลังรอการตอบสนองจากเซิร์ฟเวอร์ฐานข้อมูล คำตอบที่ได้รับการยอมรับของ Yuval เป็นวิธีที่เหมาะสมสำหรับงานที่มีขอบเขต IO
สตีเฟ่นเคนเนดี

@StephenKennedy คุณช่วยอธิบายความชัดเจนของค่าใช้จ่ายแบบไหนและมันมีผลต่อประสิทธิภาพการทำงานมากแค่ไหน? ขอบคุณ!
Yehor Hromadskyi

นั่นจะค่อนข้างยากที่จะสรุปในกล่องความคิดเห็น :) ฉันขอแนะนำให้อ่านบทความของ Stephen Cleary - เขาเป็นผู้เชี่ยวชาญในเรื่องนี้ เริ่มที่นี่: blog.stephencleary.com/2013/10/…
Stephen Kennedy

-1

ฉันได้เตรียมโค้ดไว้หนึ่งชิ้นเพื่อแสดงวิธีใช้งานสำหรับบางสถานการณ์เหล่านี้

    // method to run tasks in a parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks) {

        await Task.WhenAll(tasks);
    }
    // methode to run task one by one 
    public async Task RunMultipleTaskOneByOne(Task[] tasks)
    {
        for (int i = 0; i < tasks.Length - 1; i++)
            await tasks[i];
    }
    // method to run i task in parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks, int i)
    {
        var countTask = tasks.Length;
        var remainTasks = 0;
        do
        {
            int toTake = (countTask < i) ? countTask : i;
            var limitedTasks = tasks.Skip(remainTasks)
                                    .Take(toTake);
            remainTasks += toTake;
            await RunMultipleTaskParallel(limitedTasks.ToArray());
        } while (remainTasks < countTask);
    }

1
รับผลลัพธ์ของ Tasks อย่างไร ตัวอย่างเช่นสำหรับการผสาน "แถว" (จากงาน N แบบขนาน) ใน DataTable และผูกไว้กับ gridview asp.net?
PreguntonCojoneroCabrón
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.