การเริ่มต้นอาจไม่ถูกเรียกใช้ในงานลักษณะสัญญา ข้อยกเว้นกำลังจะมาถึง


109

ฉันกำลังสร้างแอปพลิเคชันเดสก์ท็อป wpf แบบธรรมดา UI มีเพียงปุ่มและรหัสในไฟล์. cs เช่น.

private void Button_Click_2(object sender, RoutedEventArgs e)
{
    FunctionA();
}

public void FunctionA()
{
    Task.Delay(5000).Start();
    MessageBox.Show("Waiting Complete");
}

แต่ที่น่าแปลกใจTask.Delay(5000).Start();คือการขว้างปาInvalidOperationException:

การเริ่มต้นอาจไม่ถูกเรียกใช้ในงานลักษณะสัญญา

มีใครช่วยได้ไหมว่าทำไมถึงเป็นแบบนี้?

คำตอบ:


171

คุณได้รับข้อผิดพลาดนั้นเนื่องจากTaskชั้นเรียนได้เริ่มงานก่อนที่จะมอบให้กับคุณ คุณควรเรียกStartใช้เฉพาะงานที่คุณสร้างขึ้นโดยเรียกตัวสร้างและคุณไม่ควรทำเช่นนั้นเว้นแต่คุณจะมีเหตุผลที่น่าสนใจที่จะไม่เริ่มงานเมื่อคุณสร้างขึ้น ถ้าคุณต้องการที่จะเริ่มต้นทันทีที่คุณควรใช้Task.Runหรือทั้งจำทั้งสร้างและเริ่มต้นใหม่Task.Factory.StartNewTask

Startดังนั้นตอนนี้เรารู้ว่าเพียงกำจัดน่าชังว่า คุณจะเรียกใช้รหัสของคุณและพบว่ากล่องข้อความนั้นปรากฏขึ้นทันทีไม่ใช่ 5 วินาทีต่อมาเกิดอะไรขึ้น?

ดีTask.Delayเพียงแค่ช่วยให้คุณมีงานที่จะแล้วเสร็จใน 5 วินาที ไม่หยุดการทำงานของเธรดเป็นเวลา 5 วินาที สิ่งที่คุณต้องการทำคือมีโค้ดบางอย่างที่เรียกใช้งานหลังจากงานนั้นเสร็จสิ้น นั่นคือสิ่งที่ContinueWithมีไว้สำหรับ ช่วยให้คุณสามารถรันโค้ดหลังจากทำงานที่กำหนดเสร็จแล้ว:

public void FunctionA()
{
    Task.Delay(5000)
    .ContinueWith(t => 
    {
        MessageBox.Show("Waiting Complete");
    });
}

สิ่งนี้จะทำงานตามที่คาดไว้

นอกจากนี้เรายังสามารถใช้ประโยชน์จากawaitคำหลักของ C # 5.0 เพื่อเพิ่มความต่อเนื่องได้ง่ายขึ้น:

public async Task FunctionA()
{
    await Task.Delay(5000);
    MessageBox.Show("Waiting Complete");
}

ในขณะที่คำอธิบายทั้งหมดของสิ่งที่เกิดขึ้นที่นี่อยู่นอกเหนือขอบเขตของคำถามนี้ แต่ผลลัพธ์ที่ได้คือวิธีการที่คล้ายกับวิธีการก่อนหน้านี้มาก มันจะแสดงกล่องข้อความ 5 วินาทีหลังจากที่คุณเรียกใช้เมธอด แต่เมธอดนั้นจะกลับมา [เกือบ] ทันทีในทั้งสองกรณี กล่าวได้ว่าawaitมีประสิทธิภาพมากและช่วยให้เราสามารถเขียนวิธีการที่ดูเหมือนง่ายและตรงไปตรงมา แต่จะยากกว่ามากและยากกว่าที่จะเขียนโดยใช้ContinueWithโดยตรง นอกจากนี้ยังช่วยลดความยุ่งยากในการจัดการกับข้อผิดพลาดอย่างมากโดยนำโค้ดสำเร็จรูปจำนวนมากออกไป



-4

ดังที่ Servy กล่าวไว้งานได้เริ่มขึ้นแล้วดังนั้นสิ่งที่คุณต้องทำคือรอ (. รอ ()):

private void Button_Click_2(object sender, RoutedEventArgs e)
{
    FunctionA();
}
public void FunctionA()
{
    Task.Delay(5000).Wait();
    MessageBox.Show("Waiting Complete");
}

1
การเรียกWait()ใช้งานจะบล็อกเธรดปัจจุบันจนกว่างานจะได้รับการแก้ไข นั่นแทบจะไม่ใช่สิ่งที่คุณต้องการให้เกิดขึ้น
Jeremy

1
@Jeremy: อันที่จริงมันควรค่าแก่การใส่ใจกับพฤติกรรมที่คุณพูดถึง แต่ในกรณีนี้ FunctionA ของเขาได้บล็อกเธรดปัจจุบันอยู่แล้วดังนั้นฉันจึงคิดว่าเขากำลังมองหาวิธีที่จะตัดสินว่างานเสร็จสิ้นเมื่อใด เพื่อชี้แจงความแตกต่างระหว่าง Wait และ async (สำหรับผู้อ่านในอนาคต) โปรดอ่านลิงก์
Sergiu
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.