รองานหลายงานที่มีผลลัพธ์แตกต่างกัน


237

ฉันมี 3 งาน:

private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}

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

ฉันจะโทรและรอให้งานทั้ง 3 เสร็จสมบูรณ์แล้วจึงได้ผลลัพธ์


25
คุณมีข้อกำหนดในการสั่งซื้อหรือไม่? นั่นคือคุณไม่ต้องการขายบ้านจนกว่าจะได้รับอาหารแมวหรือไม่?
Eric Lippert

คำตอบ:


411

หลังจากที่คุณใช้WhenAllคุณสามารถดึงผลลัพธ์ออกมาทีละรายการด้วยawait:

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

await Task.WhenAll(catTask, houseTask, carTask);

var cat = await catTask;
var house = await houseTask;
var car = await carTask;

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


83
คุณสามารถลบสิ่งWhenAllนี้ออกได้อย่างสิ้นเชิง การรอคอยจะดูแลให้มั่นใจว่าคุณจะไม่ย้ายงานที่ได้รับมอบหมายในภายหลังทั้ง 3 รายการจนกว่างานจะเสร็จสมบูรณ์
Servy

134
Task.WhenAll()อนุญาตให้เรียกใช้งานในโหมดขนาน ฉันไม่เข้าใจว่าทำไม @Servy จึงแนะนำให้นำออก หากไม่มีWhenAllพวกเขาจะถูกเรียกใช้ทีละคน
Sergey G.

87
@Sergey: งานเริ่มต้นดำเนินการทันที เช่นทำงานอยู่แล้วตามเวลาที่มันกลับมาจากcatTask FeedCatดังนั้นวิธีการอย่างใดอย่างหนึ่งจะทำงาน - คำถามเดียวคือว่าคุณต้องการให้awaitพวกเขาทีละคนหรือทั้งหมดเข้าด้วยกัน การจัดการข้อผิดพลาดจะแตกต่างกันเล็กน้อย - ถ้าคุณใช้Task.WhenAllมันจะทำให้awaitพวกเขาทั้งหมดแม้ว่าหนึ่งในนั้นจะล้มเหลว แต่เนิ่นๆ
Stephen Cleary

23
@Sergey Calling WhenAllไม่มีผลกระทบเมื่อการดำเนินการดำเนินการหรือวิธีที่พวกเขาดำเนินการ มีเพียงความเป็นไปได้ที่จะมีผลกระทบต่อวิธีการสังเกตผลลัพธ์ ในกรณีพิเศษนี้ความแตกต่างเพียงอย่างเดียวคือข้อผิดพลาดในหนึ่งในสองวิธีแรกจะส่งผลให้เกิดข้อยกเว้นที่เกิดขึ้นในสแตกการโทรก่อนหน้านี้ในวิธีการของฉันมากกว่าของสตีเฟ่น (แม้ว่าข้อผิดพลาดเดียวกันจะถูกโยนทิ้งเสมอ )
Servy

37
@Sergey: กุญแจสำคัญคือวิธีการแบบอะซิงโครนัสจะส่งคืนงาน "ร้อน" (เริ่มต้นแล้ว) เสมอ
สตีเฟ่นเคลียร์

99

เพียงawaitสามงานแยกจากกันหลังจากเริ่มต้นทั้งหมด

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

var cat = await catTask;
var house = await houseTask;
var car = await carTask;

8
@Bargitta ไม่นั่นมันผิด พวกเขาจะทำงานของพวกเขาในแบบคู่ขนาน รู้สึกอิสระที่จะเรียกใช้และดูตัวเอง
Servy

5
ผู้คนถามคำถามเดียวกันหลายปีมาแล้ว ... ฉันรู้สึกว่ามันเป็นเรื่องสำคัญที่จะต้องเครียดอีกครั้งว่างาน " เริ่มสร้าง " ในเนื้อหาของคำตอบ : บางทีพวกเขาอาจไม่สนใจอ่านความคิดเห็น

9
@StephenYork การเพิ่มTask.WhenAllการเปลี่ยนแปลงไม่ได้เกี่ยวกับพฤติกรรมของโปรแกรม แต่อย่างใด เป็นการเรียกเมธอดซ้ำซ้อนอย่างหมดจด คุณสามารถเพิ่มได้หากคุณต้องการเป็นตัวเลือกสุนทรียะ แต่ไม่เปลี่ยนสิ่งที่รหัสทำ เวลาดำเนินการของรหัสที่จะเหมือนกันมีหรือไม่มีวิธีการโทร (ดีในทางเทคนิคมีจะเป็นขนาดเล็กมากค่าใช้จ่ายสำหรับการโทรWhenAllแต่ควรจะมีเพียงเล็กน้อย) เพียงทำให้รุ่นที่เล็กน้อยอีกต่อไปที่จะเรียกใช้กว่ารุ่นนี้
Servy

4
@StephenYork ตัวอย่างของคุณรันการดำเนินงานตามลำดับด้วยเหตุผลสองประการ วิธีการแบบอะซิงโครนัสของคุณไม่ได้แบบอะซิงโครนัสจริงๆมันเป็นแบบซิงโครนัส ความจริงที่ว่าคุณมีวิธีการซิงโครนัสที่ส่งคืนงานที่เสร็จสมบูรณ์แล้วมักจะป้องกันไม่ให้ทำงานพร้อมกัน ถัดไปคุณไม่ได้ทำสิ่งที่ปรากฏในคำตอบนี้สำหรับการเริ่มต้นวิธีการแบบอะซิงโครนัสทั้งสามและจากนั้นรอสามงาน ตัวอย่างของคุณไม่ได้เรียกแต่ละวิธีจนกระทั่งก่อนหน้านี้เสร็จสิ้นดังนั้นจึงป้องกันไม่ให้มีการเริ่มต้นจนกว่าจะเสร็จสิ้นก่อนหน้าซึ่งแตกต่างจากรหัสนี้
Servy

4
@MarcvanNieuwenhuijzen นั่นไม่จริงอย่างที่เห็นในความคิดเห็นที่นี่และคำตอบอื่น ๆ การเพิ่มWhenAllเป็นการเปลี่ยนแปลงที่สวยงามอย่างหมดจด ความแตกต่างที่สังเกตได้เพียงอย่างเดียวคือว่าคุณจะรอให้งานในภายหลังเสร็จสิ้นหรือไม่หากงานก่อนหน้าเกิดข้อผิดพลาดซึ่งโดยทั่วไปไม่จำเป็นต้องทำ หากคุณไม่เชื่อคำอธิบายมากมายว่าทำไมข้อความของคุณไม่เป็นความจริงคุณสามารถเรียกใช้รหัสด้วยตัวคุณเองแล้วเห็นว่ามันไม่จริง
Servy

37

หากคุณใช้ C # 7 คุณสามารถใช้วิธีการห่อหุ้มที่มีประโยชน์เช่นนี้ ...

public static class TaskEx
{
    public static async Task<(T1, T2)> WhenAll<T1, T2>(Task<T1> task1, Task<T2> task2)
    {
        return (await task1, await task2);
    }
}

... เพื่อเปิดใช้งานไวยากรณ์ที่สะดวกเช่นนี้เมื่อคุณต้องการรองานหลายงานที่มีประเภทผลตอบแทนต่างกัน คุณต้องทำให้หลายงานมากเกินสำหรับงานที่แตกต่างกันเพื่อรอแน่นอน

var (someInt, someString) = await TaskEx.WhenAll(GetIntAsync(), GetStringAsync());

อย่างไรก็ตามดูคำตอบของ Marc Gravell สำหรับการปรับแต่งบางอย่างเกี่ยวกับ ValueTask และงานที่เสร็จสมบูรณ์แล้วหากคุณตั้งใจจะเปลี่ยนตัวอย่างนี้ให้เป็นสิ่งที่เป็นจริง


Tuples เป็นคุณลักษณะ C # 7 เดียวที่เกี่ยวข้องที่นี่ เหล่านี้แน่นอนในรุ่นสุดท้าย
Joel Mueller

ฉันรู้เกี่ยวกับสิ่งอันดับและ c # 7 ฉันหมายถึงฉันไม่สามารถหาวิธีที่ WhenAll ซึ่งคืนค่า tuples เนมสเปซ / แพ็คเกจอะไร
Yury Scherbakov

@YuryShcherbakov Task.WhenAll()ไม่ส่งคืน tuple สิ่งหนึ่งกำลังถูกสร้างขึ้นจากResultคุณสมบัติของภารกิจที่จัดเตรียมไว้หลังจากภารกิจส่งคืนโดยTask.WhenAll()สมบูรณ์
Chris Charabaruk

2
ฉันขอแนะนำให้แทนที่การ.Resultโทรตามเหตุผลของสตีเฟ่นเพื่อหลีกเลี่ยงคนอื่นที่ทำให้การปฏิบัติไม่ดีโดยการคัดลอกตัวอย่างของคุณ
julealgon

ฉันสงสัยว่าทำไมวิธีนี้ไม่ได้เป็นส่วนหนึ่งของกรอบนี้ ดูเหมือนว่ามีประโยชน์มาก พวกเขาหมดเวลาและต้องหยุดที่ประเภทคืนเดียว?
Ian Grainger

14

รับสามงาน - FeedCat(), SellHouse()และBuyCar()มีสองกรณีที่น่าสนใจ: ทั้งพวกเขาทั้งหมดพร้อมสมบูรณ์ (ด้วยเหตุผลบางอย่างอาจจะแคชหรือข้อผิดพลาด) หรือพวกเขาไม่ได้

สมมติว่าเรามีจากคำถาม:

Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();
    // what here?
}

ตอนนี้วิธีการง่ายๆก็คือ:

Task.WhenAll(x, y, z);

แต่ ... ที่ไม่สะดวกในการประมวลผลผลลัพธ์ โดยทั่วไปแล้วเราต้องการawait:

async Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();

    await Task.WhenAll(x, y, z);
    // presumably we want to do something with the results...
    return DoWhatever(x.Result, y.Result, z.Result);
}

แต่จะมีค่าใช้จ่ายมากมายและจัดสรรอาร์เรย์ต่าง ๆ (รวมถึงparams Task[]อาร์เรย์) และรายการ (ภายใน) มันใช้งานได้ แต่มันไม่ได้เป็น IMO ที่ยอดเยี่ยม ในหลาย ๆ วิธีมันง่ายกว่าที่จะใช้การasyncดำเนินการและawaitในแต่ละทางกลับกัน:

async Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();

    // do something with the results...
    return DoWhatever(await x, await y, await z);
}

ตรงกันข้ามกับความคิดเห็นบางส่วนด้านบนการใช้awaitแทนที่จะTask.WhenAllทำให้ไม่มีความแตกต่างกับวิธีการทำงาน (พร้อมกันเรียงตามลำดับ ฯลฯ ) ในระดับสูงสุดTask.WhenAll ถือกำเนิดสนับสนุนคอมไพเลอร์ที่ดีสำหรับasync/ awaitและเป็นประโยชน์เมื่อสิ่งเหล่านั้นไม่ได้อยู่ นอกจากนี้ยังมีประโยชน์เมื่อคุณมีงานตามอำเภอใจมากกว่า 3 งานที่รอบคอบ

แต่: เรายังคงมีปัญหาที่async/ awaitสร้างเสียงคอมไพเลอร์จำนวนมากเพื่อความต่อเนื่อง หากเป็นไปได้ว่างานอาจเสร็จสมบูรณ์แบบซิงโครนัสแล้วเราสามารถเพิ่มประสิทธิภาพนี้โดยการสร้างในเส้นทางซิงโครนัสที่มีทางเลือกแบบอะซิงโครนัส:

Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();

    if(x.Status == TaskStatus.RanToCompletion &&
       y.Status == TaskStatus.RanToCompletion &&
       z.Status == TaskStatus.RanToCompletion)
        return Task.FromResult(
          DoWhatever(a.Result, b.Result, c.Result));
       // we can safely access .Result, as they are known
       // to be ran-to-completion

    return Awaited(x, y, z);
}

async Task Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
    return DoWhatever(await x, await y, await z);
}

วิธีการ "ซิงค์เส้นทางพร้อมทางเลือกสำรอง async" นี้เป็นเรื่องปกติที่พบเห็นได้ทั่วไปโดยเฉพาะอย่างยิ่งในรหัสประสิทธิภาพสูง โปรดทราบว่าจะไม่ช่วยเลยหากการดำเนินการเสร็จสิ้นจะไม่พร้อมกันอย่างแท้จริง

สิ่งเพิ่มเติมที่ใช้ที่นี่:

  1. ด้วย C # เมื่อเร็ว ๆ นี้รูปแบบทั่วไปสำหรับasyncเมธอด fallback นั้นถูกนำไปใช้เป็นฟังก์ชันโลคัล:

    Task<string> DoTheThings() {
        async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
            return DoWhatever(await a, await b, await c);
        }
        Task<Cat> x = FeedCat();
        Task<House> y = SellHouse();
        Task<Tesla> z = BuyCar();
    
        if(x.Status == TaskStatus.RanToCompletion &&
           y.Status == TaskStatus.RanToCompletion &&
           z.Status == TaskStatus.RanToCompletion)
            return Task.FromResult(
              DoWhatever(a.Result, b.Result, c.Result));
           // we can safely access .Result, as they are known
           // to be ran-to-completion
    
        return Awaited(x, y, z);
    }
  2. ต้องการValueTask<T>ไปTask<T>ถ้ามีโอกาสที่ดีของสิ่งที่เคยสมบูรณ์พร้อมกับหลายค่าผลตอบแทนที่แตกต่างกัน

    ValueTask<string> DoTheThings() {
        async ValueTask<string> Awaited(ValueTask<Cat> a, Task<House> b, Task<Tesla> c) {
            return DoWhatever(await a, await b, await c);
        }
        ValueTask<Cat> x = FeedCat();
        ValueTask<House> y = SellHouse();
        ValueTask<Tesla> z = BuyCar();
    
        if(x.IsCompletedSuccessfully &&
           y.IsCompletedSuccessfully &&
           z.IsCompletedSuccessfully)
            return new ValueTask<string>(
              DoWhatever(a.Result, b.Result, c.Result));
           // we can safely access .Result, as they are known
           // to be ran-to-completion
    
        return Awaited(x, y, z);
    }
  3. ถ้าเป็นไปได้ต้องการIsCompletedSuccessfullyที่จะStatus == TaskStatus.RanToCompletion; ตอนนี้มีอยู่ใน. NET Core สำหรับTaskและทุกที่ValueTask<T>


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

@Servy คุณพูดถูกนั่นคือความคิดเห็น ฉันจะเพิ่มการปรับแต่งเพื่อแสดงโดยใช้ผลลัพธ์
Marc Gravell

@Servy tweak ได้เพิ่ม
Marc Gravell

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

@Servy ที่เป็นหัวข้อที่ซับซ้อน - คุณได้รับความหมายที่แตกต่างจากสองสถานการณ์ - รอการทริกเกอร์ข้อยกเว้นทำงานแตกต่างจากการเข้าถึง. Result เพื่อทริกเกอร์ข้อยกเว้น IMO ณ จุดนั้นเราควรawaitจะได้ความหมายข้อยกเว้นที่ "ดีกว่า" บนสมมติฐานที่ว่าข้อยกเว้นนั้นหายาก แต่มีความหมาย
Marc Gravell

12

คุณสามารถจัดเก็บไว้ในงานแล้วรอพวกเขาทั้งหมด:

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

await Task.WhenAll(catTask, houseTask, carTask);

Cat cat = await catTask;
House house = await houseTask;
Car car = await carTask;

ไม่ได้var catTask = FeedCat()ใช้ฟังก์ชั่นFeedCat()และเก็บผลลัพธ์ไว้ในcatTaskการทำให้ชิ้นawait Task.WhenAll()ส่วนไร้ประโยชน์เนื่องจากวิธีการทำงานแล้ว?
Kraang Prime

1
@sanuel หากพวกเขากลับมางาน <t> แล้วไม่ ... พวกเขาเริ่ม async เปิด แต่ไม่ต้องรอ
Reed Copsey

ฉันไม่คิดว่ามันถูกต้องโปรดดูการอภิปรายภายใต้คำตอบของ @ StephenCleary ... ดูคำตอบของ Servy
Rosdi Kasim

1
ถ้าฉันต้องการเพิ่ม. ConfigrtueAwait (false) ฉันจะเพิ่มมันใน Task ได้ทุกเมื่อหรือต่อพนักงานเสิร์ฟทุกคนที่ตามมา?
AstroSharp

@AstroSharp โดยทั่วไปเป็นความคิดที่ดีที่จะเพิ่มลงในพวกเขาทั้งหมด (ถ้ารายการแรกเสร็จสมบูรณ์จะถูกละเว้นอย่างมีประสิทธิภาพ) แต่ในกรณีนี้มันอาจจะโอเคที่จะทำสิ่งแรก - เว้นแต่จะมี async มากกว่า สิ่งที่เกิดขึ้นในภายหลัง
Reed Copsey

6

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

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

await Task.WhenAll(catTask, houseTask, carTask);

var cat = await catTask;
var house = await houseTask;
var car = await carTask;

ลองนึกภาพ FeedCat พ่นข้อยกเว้นในรหัสต่อไปนี้:

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

var cat = await catTask;
var house = await houseTask;
var car = await carTask;

ในกรณีนี้คุณจะไม่รอคอยบน houseTask หรือ carTask มี 3 สถานการณ์ที่เป็นไปได้ที่นี่:

  1. SellHouse เสร็จสมบูรณ์แล้วเมื่อ FeedCat ล้มเหลว ในกรณีนี้คุณสบายดี

  2. SellHouse ไม่สมบูรณ์และล้มเหลวโดยมีข้อยกเว้นในบางจุด ไม่พบข้อยกเว้นและจะถูกส่งซ้ำในเธรด finalizer

  3. SellHouse ยังไม่สมบูรณ์และมีการรอคอยอยู่ข้างใน ในกรณีที่รหัสของคุณทำงานใน ASP.NET SellHouse จะล้มเหลวทันทีที่การรอคอยบางอย่างเสร็จสิ้นภายใน สิ่งนี้เกิดขึ้นเพราะคุณทำให้เกิดไฟไหม้ & ลืมการโทรและบริบทการซิงโครไนซ์ก็หายไปทันทีที่ FeedCat ล้มเหลว

นี่คือข้อผิดพลาดที่คุณจะได้รับสำหรับเคส (3):

System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
   at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
   at System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
   at System.Web.Util.SynchronizationHelper.SafeWrapCallback(Action action)
   at System.Threading.Tasks.Task.Execute()
   --- End of inner exception stack trace ---
---> (Inner Exception #0) System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
   at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
   at System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
   at System.Web.Util.SynchronizationHelper.SafeWrapCallback(Action action)
   at System.Threading.Tasks.Task.Execute()<---

สำหรับกรณี (2) คุณจะได้รับข้อผิดพลาดที่คล้ายกัน แต่ด้วยการติดตามสแต็กข้อยกเว้นดั้งเดิม

สำหรับ. NET 4.0 ขึ้นไปคุณสามารถตรวจจับข้อยกเว้นที่ไม่ได้รับการตรวจสอบได้โดยใช้ TaskScheduler.UnobservedTaskException สำหรับ. NET 4.5 และข้อยกเว้นที่ไม่ได้ตรวจสอบในภายหลังจะถูกกลืนโดยค่าเริ่มต้นสำหรับ. NET 4.0 ข้อยกเว้นที่ไม่ได้ตรวจสอบจะขัดข้องกระบวนการของคุณ

รายละเอียดเพิ่มเติมที่นี่: การจัดการข้อยกเว้นงานใน. NET 4.5


2

คุณสามารถใช้Task.WhenAllตามที่กล่าวไว้หรือTask.WaitAllขึ้นอยู่กับว่าคุณต้องการให้เธรดรอหรือไม่ ลองดูลิงค์สำหรับคำอธิบายของทั้งสอง

WaitAll vs WhenAll


2

ใช้Task.WhenAllแล้วรอผล:

var tCat = FeedCat();
var tHouse = SellHouse();
var tCar = BuyCar();
await Task.WhenAll(tCat, tHouse, tCar);
Cat cat = await tCat;
House house = await tHouse;
Tesla car = await tCar; 
//as they have all definitely finished, you could also use Task.Value.

มม. ... ไม่ใช่ Task.Value (อาจเคยมีอยู่ในปี 2013 ใช่ไหม) แทนที่จะเป็น tCat.Result, tHouse.Result หรือ tCar.Result
Stephen York

1

คำเตือนล่วงหน้า

เพียงแค่ Headsup สั้น ๆ สำหรับผู้ที่เข้ามาเยี่ยมชมและกระทู้อื่น ๆ ที่คล้ายกันนี้กำลังมองหาวิธีการขนาน EntityFramework โดยใช้ async + await + task-set task : รูปแบบที่แสดงที่นี่เป็นเสียง แต่เมื่อถึงเกล็ดหิมะพิเศษของ EF คุณจะไม่ บรรลุการประมวลผลแบบขนานเว้นแต่และจนกว่าคุณจะใช้ db-context-instance แยกต่างหากภายในการโทร * Async () แต่ละครั้งและที่เกี่ยวข้อง

การเรียงลำดับของสิ่งนี้มีความจำเป็นเนื่องจากข้อ จำกัด การออกแบบโดยธรรมชาติของ ef-db-contexts ซึ่งห้ามไม่ให้รันหลายเคียวรีแบบขนานในอินสแตนซ์ ef-db-context เดียวกัน


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

  public async Task<string> Foobar() {
    async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
        return DoSomething(await a, await b, await c);
    }

    using (var carTask = BuyCarAsync())
    using (var catTask = FeedCatAsync())
    using (var houseTask = SellHouseAsync())
    {
        if (carTask.Status == TaskStatus.RanToCompletion //triple
            && catTask.Status == TaskStatus.RanToCompletion //cache
            && houseTask.Status == TaskStatus.RanToCompletion) { //hits
            return Task.FromResult(DoSomething(catTask.Result, carTask.Result, houseTask.Result)); //fast-track
        }

        cat = await catTask;
        car = await carTask;
        house = await houseTask;
        //or Task.AwaitAll(carTask, catTask, houseTask);
        //or await Task.WhenAll(carTask, catTask, houseTask);
        //it depends on how you like exception handling better

        return Awaited(catTask, carTask, houseTask);
   }
 }

การใช้งานทางเลือกที่มีคุณสมบัติด้านประสิทธิภาพที่เหมือนกันอาจมากหรือน้อย:

 public async Task<string> Foobar() {
    using (var carTask = BuyCarAsync())
    using (var catTask = FeedCatAsync())
    using (var houseTask = SellHouseAsync())
    {
        cat = catTask.Status == TaskStatus.RanToCompletion ? catTask.Result : (await catTask);
        car = carTask.Status == TaskStatus.RanToCompletion ? carTask.Result : (await carTask);
        house = houseTask.Status == TaskStatus.RanToCompletion ? houseTask.Result : (await houseTask);

        return DoSomething(cat, car, house);
     }
 }

-1
var dn = await Task.WhenAll<dynamic>(FeedCat(),SellHouse(),BuyCar());

ถ้าคุณต้องการเข้าถึง Cat คุณต้องทำสิ่งนี้:

var ct = (Cat)dn[0];

มันง่ายมากที่จะทำและมีประโยชน์มากที่จะใช้ไม่จำเป็นต้องไปแก้ปัญหาที่ซับซ้อน


1
มีปัญหาเดียวกับสิ่งนี้dynamicคือปีศาจ สำหรับการทำงานร่วมกันของ COM ที่ยุ่งยากและไม่ควรใช้ในทุกสถานการณ์ที่ไม่จำเป็นอย่างยิ่ง โดยเฉพาะถ้าคุณใส่ใจเรื่องประสิทธิภาพ หรือพิมพ์ความปลอดภัย หรือ refactoring หรือแก้ไขข้อบกพร่อง
Joel Mueller
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.