Async C # 5 จะสนับสนุนปัญหาการซิงโครไนซ์เธรด UI อย่างไร


16

ฉันได้ยินบางที่ Cync ที่รอคอย async จะยอดเยี่ยมมากจนคุณไม่ต้องกังวลเกี่ยวกับการทำเช่นนี้:

if (InvokeRequired)
{
    BeginInvoke(...);
    return;
}
// do your stuff here

ดูเหมือนว่าการเรียกกลับของการดำเนินการที่รอจะเกิดขึ้นในเธรดต้นฉบับของผู้โทร มีการระบุไว้หลายครั้งโดย Eric Lippert และ Anders Hejlsberg ว่าคุณลักษณะนี้มีต้นกำเนิดมาจากความต้องการที่จะทำให้ UIs (โดยเฉพาะ UIs อุปกรณ์สัมผัส) ตอบสนองได้ดียิ่งขึ้น

ฉันคิดว่าการใช้คุณสมบัติทั่วไปเช่นนี้จะเป็นดังนี้:

public class Form1 : Form
{
    // ...
    async void GetFurtherInfo()
    {
        var temperature = await GetCurrentTemperatureAsync();
        label1.Text = temperature;
    }
}

หากใช้การติดต่อกลับเพียงอย่างเดียวการตั้งค่าข้อความป้ายกำกับจะทำให้เกิดข้อยกเว้นเนื่องจากไม่มีการดำเนินการในเธรดของ UI

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

โปรดระบุลิงก์จากแหล่งที่เชื่อถือได้อย่าเพิ่งตอบ "ใช่"


เรื่องนี้ดูเหมือนจะไม่สูงอย่างน้อยตราบเท่าที่awaitฟังก์ชั่นที่เกี่ยวข้อง มันเป็นเพียงแค่จำนวนมากของน้ำตาลประโยคสำหรับความต่อเนื่องผ่าน อาจมีการปรับปรุงอื่น ๆ ที่ไม่เกี่ยวข้องกับ WinForms ที่ควรช่วย? ที่จะตกอยู่ภายใต้กรอบ NET. ตัวเองแม้ว่าและไม่ C # โดยเฉพาะ
Aaronaught

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

คำตอบ:


17

ฉันคิดว่าคุณจะสับสนเล็กน้อยที่นี่ สิ่งที่คุณกำลังถามหาอยู่แล้วไปได้โดยใช้System.Threading.Tasksการasyncและawaitใน C # 5 เพียงแค่จะให้เล็ก ๆ น้อย ๆ ดีกว่าน้ำตาลประโยคสำหรับคุณลักษณะเดียวกัน

ลองใช้ตัวอย่าง Winforms - วางปุ่มและกล่องข้อความในแบบฟอร์มและใช้รหัสนี้:

private void button1_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew<int>(() => DelayedAdd(5, 10))
        .ContinueWith(t => DelayedAdd(t.Result, 20))
        .ContinueWith(t => DelayedAdd(t.Result, 30))
        .ContinueWith(t => DelayedAdd(t.Result, 50))
        .ContinueWith(t => textBox1.Text = t.Result.ToString(),
            TaskScheduler.FromCurrentSynchronizationContext());
}

private int DelayedAdd(int a, int b)
{
    Thread.Sleep(500);
    return a + b;
}

เรียกใช้และคุณจะเห็นว่า (a) ไม่ได้บล็อกเธรด UI และ (b) คุณไม่ได้รับข้อผิดพลาด "การดำเนินการข้ามเธรดที่ไม่ถูกต้อง" ตามปกติ - ยกเว้นว่าคุณลบTaskSchedulerอาร์กิวเมนต์ออกจากครั้งสุดท้ายContinueWithใน ในกรณีนี้คุณจะ

นี้เป็นที่ลุ่มมาตรฐานรูปแบบต่อเนื่องผ่าน ความมหัศจรรย์ที่เกิดขึ้นในชั้นเรียนและโดยเฉพาะเช่นเรียกโดยTaskScheduler FromCurrentSynchronizationContextผ่านสิ่งนี้ไปยังการต่อเนื่องใด ๆ และคุณบอกว่าการต่อเนื่องนั้นจะต้องดำเนินการกับเธรดใดก็ตามที่เรียกว่าFromCurrentSynchronizationContextเมธอด - ในกรณีนี้คือเธรด UI

ผู้รอดูมีความซับซ้อนมากขึ้นเล็กน้อยในแง่ที่ว่าพวกเขารู้ว่าพวกเขาเริ่มหัวข้อใดและหัวข้อใดที่ความต่อเนื่องต้องเกิดขึ้น ดังนั้นรหัสข้างต้นสามารถเขียนเล็ก ๆ น้อย ๆขึ้นตามธรรมชาติ:

private async void button1_Click(object sender, EventArgs e)
{
    int a = await DelayedAddAsync(5, 10);
    int b = await DelayedAddAsync(a, 20);
    int c = await DelayedAddAsync(b, 30);
    int d = await DelayedAddAsync(c, 50);
    textBox1.Text = d.ToString();
}

private async Task<int> DelayedAddAsync(int a, int b)
{
    Thread.Sleep(500);
    return a + b;
}

ทั้งสองควรมีลักษณะคล้ายกันมากและในความเป็นจริงพวกเขามีความคล้ายกันมาก DelayedAddAsyncวิธีการในขณะนี้จะส่งกลับTask<int>แทนintและเพื่อawaitเป็นเพียงการตบตบนหนึ่งในบรรดาแต่ละ ข้อแตกต่างที่สำคัญคือการส่งผ่านบริบทการซิงโครไนซ์ในแต่ละบรรทัดดังนั้นคุณไม่จำเป็นต้องทำอย่างชัดเจนเหมือนกับที่เราทำในตัวอย่างที่ผ่านมา

ในทางทฤษฎีแล้วความแตกต่างนั้นสำคัญกว่ามาก ในตัวอย่างที่สองทุกบรรทัดเดียวในbutton1_Clickวิธีจะถูกดำเนินการจริงในเธรด UI แต่งานของตัวเอง ( DelayedAddAsync) ทำงานในพื้นหลัง ในตัวอย่างแรกทุกอย่างวิ่งในพื้นหลัง , ยกเว้นสำหรับการมอบหมายในการtextBox1.Textที่เราได้แนบอย่างชัดเจนกับบริบทการประสานหัวข้อ UI ของ

นั่นคือสิ่งที่น่าสนใจจริง ๆawait- ความจริงที่ว่าผู้รอคอยสามารถกระโดดเข้าและออกจากวิธีเดียวกันโดยไม่ต้องมีการบล็อคการโทร คุณเรียกawaitใช้เธรดปัจจุบันกลับไปที่การประมวลผลข้อความและเมื่อเสร็จแล้วผู้รอคอยจะรับสิ่งที่มันค้างไว้ในเธรดเดียวกันที่เหลืออยู่ แต่ในแง่ของInvoke/ BeginInvokeความคมชัดในคำถามของฉัน ขออภัยที่ต้องบอกว่าคุณควรหยุดทำเช่นนั้นเป็นเวลานานแล้ว


@Anaught น่าสนใจมาก ฉันรับรู้ถึงรูปแบบการส่งต่อที่ต่อเนื่อง แต่ไม่ได้ตระหนักถึงสิ่งที่ "บริบทการซิงโครไนซ์" ทั้งหมดนี้ มีเอกสารใดที่เชื่อมโยงบริบทการซิงค์นี้กับ C # 5 แบบรอคอย async หรือไม่? ฉันเข้าใจว่ามันเป็นฟีเจอร์ที่มีอยู่แล้ว แต่ความจริงแล้วพวกเขาใช้มันโดยค่าเริ่มต้นฟังดูเหมือนเป็นเรื่องใหญ่เป็นพิเศษเพราะมันจะต้องมีผลกระทบอย่างมากต่อประสิทธิภาพการทำงานใช่มั้ย มีความคิดเห็นเพิ่มเติมเกี่ยวกับเรื่องนี้อีกไหม? ขอบคุณสำหรับคำตอบของคุณ
อเล็กซ์

1
@ Alex: สำหรับคำตอบให้กับทุกคนที่ติดตามคำถามผมขอแนะนำให้คุณอ่านAsync ประสิทธิภาพการทำงาน: การทำความเข้าใจกับค่าใช้จ่ายของ Async และรอคอย ส่วน "ใส่ใจเกี่ยวกับบริบท" อธิบายถึงความเกี่ยวข้องกับบริบทการซิงโครไนซ์ทั้งหมด
Aaronaught

(โดยวิธีการบริบทการซิงโครไนซ์ไม่ใช่เรื่องใหม่พวกเขาอยู่ในกรอบตั้งแต่ 2.0 TPL ทำให้พวกเขาใช้งานได้ง่ายขึ้นมาก)
Aaronaught

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

2
@ Alex: ผมคิดว่าคุณไม่ได้มองในสถานที่ที่เหมาะสม ฉันไม่รู้จะบอกอะไรคุณ มีส่วนใหญ่ของชุมชน. NET ซึ่งใช้เวลานานในการติดตาม เฮ้ฉันยังเห็น coders บางตัวใช้ArrayListclass ในรหัสใหม่ ฉันยังแทบไม่มีประสบการณ์กับ RX เลย ผู้คนเรียนรู้สิ่งที่พวกเขาต้องรู้และแบ่งปันสิ่งที่พวกเขารู้แล้วแม้ว่าสิ่งที่พวกเขารู้แล้วล้าสมัย คำตอบนี้อาจล้าสมัยในไม่กี่ปี
Aaronaught

4

ใช่สำหรับเธรด UI การเรียกกลับของการดำเนินการที่รอจะเกิดขึ้นในเธรดดั้งเดิมของผู้โทร

Eric Lippert เขียนซีรีส์ 8 ตอนทั้งหมดเกี่ยวกับเรื่องนี้เมื่อปีที่แล้ว: Fabulous Adventures In Coding

แก้ไข: และนี่คือ Anders '// build / presentation: channel9

BTW คุณสังเกตเห็นไหมว่าถ้าคุณเปลี่ยน "// build /" กลับหัวกลับหางคุณจะได้ "/ plinq //" ;-)


3

Jon Skeet มอบงานนำเสนอที่ยอดเยี่ยมซึ่งครอบคลุมวิธีการ Async ใน C # 5 ซึ่งคุณอาจพบว่ามีประโยชน์มาก:

http://skillsmatter.com/podcast/home/async-methods


EduAsyncโดยผู้เขียนคนเดียวกันอาจเป็นแหล่งข้อมูลที่ดีเกี่ยวกับ Async
Matthieu
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.