ฉันกำลังใช้ async / คอยและTask
มีจำนวนมาก แต่ไม่เคยใช้Task.Yield()
และซื่อสัตย์แม้ว่าจะมีคำอธิบายทั้งหมดฉันไม่เข้าใจว่าทำไมฉันถึงต้องใช้วิธีนี้
ใครสามารถยกตัวอย่างที่ดีที่Yield()
จำเป็น
ฉันกำลังใช้ async / คอยและTask
มีจำนวนมาก แต่ไม่เคยใช้Task.Yield()
และซื่อสัตย์แม้ว่าจะมีคำอธิบายทั้งหมดฉันไม่เข้าใจว่าทำไมฉันถึงต้องใช้วิธีนี้
ใครสามารถยกตัวอย่างที่ดีที่Yield()
จำเป็น
คำตอบ:
เมื่อคุณใช้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
Task.Run
เพื่อใช้มันExecuteFooOnUIThread
จะทำงานบนเธรดพูลไม่ใช่เธรด UI ด้วยawait Task.Yield()
คุณบังคับให้มันเป็นแบบอะซิงโครนัสในวิธีที่รหัสที่ตามมายังคงทำงานในบริบทปัจจุบัน (ในเวลาต่อมา) ไม่ใช่สิ่งที่คุณทำตามปกติ แต่เป็นเรื่องดีที่มีตัวเลือกถ้าจำเป็นด้วยเหตุผลแปลก ๆ
ExecuteFooOnUIThread()
ใช้งานนานมากจะยังคงบล็อกเธรด UI เป็นเวลานานในบางจุดและทำให้ UI ไม่ตอบสนองถูกต้องหรือไม่
ภายในawait Task.Yield()
เพียงแค่คิวต่อเนื่องทั้งบริบทการประสานในปัจจุบันหรือในหัวข้อสระว่ายน้ำแบบสุ่มถ้าเป็นSynchronizationContext.Current
null
มันคือ นำมาใช้อย่างมีประสิทธิภาพเป็นพนักงานเสิร์ฟที่กำหนดเอง โค้ดที่มีประสิทธิภาพน้อยกว่าที่สร้างเอฟเฟกต์ที่เหมือนกันอาจจะง่ายอย่างนี้:
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.StartNew
w / กำหนดการงานที่เหมาะสม
ดูสิ่งนี้ด้วย:
var dialogTask = await showAsync();
คืออะไร
var dialogTask = await showAsync()
จะไม่รวบรวมเพราะawait showAsync()
นิพจน์จะไม่ส่งกลับTask
(ซึ่งต่างจากที่ไม่มีawait
) ถ้าคุณทำเช่นawait showAsync()
นั้นการดำเนินการหลังจากนั้นจะกลับมาทำงานต่อหลังจากปิดกล่องโต้ตอบนั่นคือความแตกต่าง นั่นเป็นเพราะwindow.ShowDialog
API แบบซิงโครนัส (แม้จะยังคงปั๊มข้อความ) ในรหัสนั้นฉันต้องการดำเนินการต่อในขณะที่กล่องโต้ตอบยังคงแสดงอยู่
การใช้งานครั้งเดียว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();
}
await Task.Delay(1)
เพียงพอที่จะป้องกันมัน (แอพคอนโซล,. NET Core 3.1, C # 8)
Task.Yield()
อาจจะใช้ในการใช้งานจำลองของวิธีการ async
await Task.Yield()
บังคับให้วิธีการเป็นแบบซิงค์ทำไมเราถึงต้องเขียนโค้ด async แบบ "ของจริง" ลองนึกภาพวิธีการซิงค์หนัก ในการทำให้เป็นแบบซิงค์เพียงแค่เพิ่มasync
และawait Task.Yield()
ในจุดเริ่มต้นและอย่างน่าอัศจรรย์มันจะเป็นแบบซิงค์หรือไม่ นั่นเป็นเหมือนการห่อรหัสการซิงค์ทั้งหมดTask.Run()
และสร้างวิธีการอะซิงก์ปลอม