เมื่อใดที่ฉันจะใช้ Task.Yield ()


218

ฉันกำลังใช้ async / คอยและTaskมีจำนวนมาก แต่ไม่เคยใช้Task.Yield()และซื่อสัตย์แม้ว่าจะมีคำอธิบายทั้งหมดฉันไม่เข้าใจว่าทำไมฉันถึงต้องใช้วิธีนี้

ใครสามารถยกตัวอย่างที่ดีที่Yield()จำเป็น

คำตอบ:


241

เมื่อคุณใช้async/ awaitไม่มีการรับประกันว่าวิธีการที่คุณโทรเมื่อคุณawait FooAsync()ใช้จริงจะทำงานแบบอะซิงโครนัส การนำไปใช้ภายในมีอิสระที่จะส่งคืนโดยใช้พา ธ แบบซิงโครนัสอย่างสมบูรณ์

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

สิ่งนี้ยังมีประโยชน์หากคุณสร้างวิธีการแบบอะซิงโครนัสที่ต้องการการเริ่มต้นแบบ "ทำงานนาน" เช่น:

 private async void button_Click(object sender, EventArgs e)
 {
      await Task.Yield(); // Make us async right away

      var data = ExecuteFooOnUIThread(); // This will run on the UI thread at some point later

      await UseDataAsync(data);
 }

โดยไม่ต้องโทรวิธีการที่จะดำเนินการพร้อมกันทุกทางขึ้นไปยังสายแรกที่Task.Yield()await


26
ฉันรู้สึกว่าฉันตีความบางสิ่งผิดที่นี่ หากawait Task.Yield()บังคับให้วิธีการเป็นแบบซิงค์ทำไมเราถึงต้องเขียนโค้ด async แบบ "ของจริง" ลองนึกภาพวิธีการซิงค์หนัก ในการทำให้เป็นแบบซิงค์เพียงแค่เพิ่มasyncและawait Task.Yield()ในจุดเริ่มต้นและอย่างน่าอัศจรรย์มันจะเป็นแบบซิงค์หรือไม่ นั่นเป็นเหมือนการห่อรหัสการซิงค์ทั้งหมดTask.Run()และสร้างวิธีการอะซิงก์ปลอม
Krumelur

14
@Krumelur มีความแตกต่างใหญ่ - ดูตัวอย่างของฉัน ถ้าคุณใช้ a Task.Runเพื่อใช้มันExecuteFooOnUIThreadจะทำงานบนเธรดพูลไม่ใช่เธรด UI ด้วยawait Task.Yield()คุณบังคับให้มันเป็นแบบอะซิงโครนัสในวิธีที่รหัสที่ตามมายังคงทำงานในบริบทปัจจุบัน (ในเวลาต่อมา) ไม่ใช่สิ่งที่คุณทำตามปกติ แต่เป็นเรื่องดีที่มีตัวเลือกถ้าจำเป็นด้วยเหตุผลแปลก ๆ
Reed Copsey

7
คำถามอีกข้อหนึ่ง: หากExecuteFooOnUIThread()ใช้งานนานมากจะยังคงบล็อกเธรด UI เป็นเวลานานในบางจุดและทำให้ UI ไม่ตอบสนองถูกต้องหรือไม่
Krumelur

7
@Krumelur ใช่มันจะ ไม่ได้ทันที - มันจะเกิดขึ้นในภายหลัง
Reed Copsey

33
แม้ว่าคำตอบนี้จะถูกต้องทางเทคนิคคำสั่งที่ "ส่วนที่เหลือของรหัสจะดำเนินการในภายหลัง" เป็นนามธรรมเกินไปและอาจทำให้เข้าใจผิด กำหนดการเรียกใช้โค้ดหลังจาก Task.Yield () นั้นขึ้นอยู่กับรูปแบบการประสาน และเอกสาร MSDN ระบุไว้อย่างชัดเจนว่า "บริบทการซิงโครไนซ์ที่มีอยู่ในเธรด UI ในสภาพแวดล้อม UI ส่วนใหญ่มักจะจัดลำดับความสำคัญของงานที่โพสต์ไปยังบริบทที่สูงกว่างานอินพุตและการเรนเดอร์ด้วยเหตุผลนี้ เพื่อให้ UI ตอบสนองได้ "
Vitaliy Tsvayer

36

ภายในawait Task.Yield()เพียงแค่คิวต่อเนื่องทั้งบริบทการประสานในปัจจุบันหรือในหัวข้อสระว่ายน้ำแบบสุ่มถ้าเป็นSynchronizationContext.Currentnull

มันคือ นำมาใช้อย่างมีประสิทธิภาพเป็นพนักงานเสิร์ฟที่กำหนดเอง โค้ดที่มีประสิทธิภาพน้อยกว่าที่สร้างเอฟเฟกต์ที่เหมือนกันอาจจะง่ายอย่างนี้:

var tcs = new TaskCompletionSource<bool>();
var sc = SynchronizationContext.Current;
if (sc != null)
    sc.Post(_ => tcs.SetResult(true), null);
else
    ThreadPool.QueueUserWorkItem(_ => tcs.SetResult(true));
await tcs.Task;

Task.Yield()สามารถใช้เป็นทางลัดสำหรับการดัดแปลงการไหลของการประมวลผลแปลก ๆ ตัวอย่างเช่น:

async Task DoDialogAsync()
{
    var dialog = new Form();

    Func<Task> showAsync = async () => 
    {
        await Task.Yield();
        dialog.ShowDialog();
    }

    var dialogTask = showAsync();
    await Task.Yield();

    // now we're on the dialog's nested message loop started by dialog.ShowDialog 
    MessageBox.Show("The dialog is visible, click OK to close");
    dialog.Close();

    await dialogTask;
    // we're back to the main message loop  
}

ที่กล่าวว่าฉันไม่สามารถคิดกรณีใด ๆ ที่Task.Yield()ไม่สามารถแทนที่ด้วยTask.Factory.StartNeww / กำหนดการงานที่เหมาะสม

ดูสิ่งนี้ด้วย:


ในตัวอย่างของคุณความแตกต่างระหว่างสิ่งที่อยู่กับนั้นvar dialogTask = await showAsync();คืออะไร
Erik Philips

@ErikPhilips var dialogTask = await showAsync()จะไม่รวบรวมเพราะawait showAsync()นิพจน์จะไม่ส่งกลับTask(ซึ่งต่างจากที่ไม่มีawait) ถ้าคุณทำเช่นawait showAsync()นั้นการดำเนินการหลังจากนั้นจะกลับมาทำงานต่อหลังจากปิดกล่องโต้ตอบนั่นคือความแตกต่าง นั่นเป็นเพราะwindow.ShowDialogAPI แบบซิงโครนัส (แม้จะยังคงปั๊มข้อความ) ในรหัสนั้นฉันต้องการดำเนินการต่อในขณะที่กล่องโต้ตอบยังคงแสดงอยู่
noseratio

5

การใช้งานครั้งเดียวTask.Yield()คือเพื่อป้องกันการโอเวอร์โฟลว์สแต็กเมื่อทำการเรียกซ้ำแบบ async Task.Yield()ป้องกันการซิงค์แบบต่อเนื่อง อย่างไรก็ตามโปรดทราบว่าสิ่งนี้อาจทำให้เกิดข้อยกเว้น OutOfMemory (ตามที่ระบุไว้โดย Triynko) การเรียกซ้ำที่ไม่มีที่สิ้นสุดยังไม่ปลอดภัยและคุณอาจดีกว่าการเขียนการสอบถามซ้ำเป็นวนซ้ำ

private static void Main()
    {
        RecursiveMethod().Wait();
    }

    private static async Task RecursiveMethod()
    {
        await Task.Delay(1);
        //await Task.Yield(); // Uncomment this line to prevent stackoverlfow.
        await RecursiveMethod();
    }

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

ฉันไม่สามารถทำซ้ำสแต็คล้นได้ ดูเหมือนว่าawait Task.Delay(1)เพียงพอที่จะป้องกันมัน (แอพคอนโซล,. NET Core 3.1, C # 8)
Theodor Zoulias

-8

Task.Yield() อาจจะใช้ในการใช้งานจำลองของวิธีการ async


4
คุณควรให้รายละเอียดบางอย่าง
PJProudhon

3
เพื่อจุดประสงค์นี้ฉันควรใช้Task.CompletedTask - ดูหัวข้อ Task.CompletedTask ในโพสต์บล็อก msdn นี้สำหรับการพิจารณาเพิ่มเติม
Grzegorz Smulko

2
ปัญหาเกี่ยวกับการใช้ Task.CompletedTask หรือ Task.FromResult คือคุณอาจพลาดจุดบกพร่องที่ปรากฏขึ้นเมื่อวิธีดำเนินการแบบอะซิงโครนัสเท่านั้น
Joakim MH
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.