วิงวอน (Delegate)


93

ใครก็ได้โปรดอธิบายข้อความนี้ที่เขียนไว้ในลิงค์นี้

Invoke(Delegate):

ดำเนินการผู้รับมอบสิทธิ์ที่ระบุบนเธรดที่เป็นเจ้าของหมายเลขอ้างอิงหน้าต่างของตัวควบคุม

ใครสามารถอธิบายความหมาย (โดยเฉพาะตัวหนา) ฉันไม่สามารถเข้าใจได้อย่างชัดเจน


4
คำตอบสำหรับคำถามนี้เชื่อมโยงกับคุณสมบัติ Control.InvokeRequired - โปรดดูmsdn.microsoft.com/en-us/library/…
dash

คำตอบ:


131

คำตอบสำหรับคำถามนี้อยู่ที่การควบคุม C #

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

จากControl.InvokeRequired

อย่างมีประสิทธิภาพสิ่งที่ Invoke ทำคือให้แน่ใจว่ารหัสที่คุณเรียกใช้เกิดขึ้นบนเธรดที่ตัวควบคุม "ใช้งานอยู่" ได้อย่างมีประสิทธิภาพป้องกันข้อยกเว้นของเธรด

จากมุมมองทางประวัติศาสตร์ใน. Net 1.1 สิ่งนี้ได้รับอนุญาตจริง ความหมายก็คือคุณสามารถลองรันโค้ดบนเธรด "GUI" จากเธรดพื้นหลังใดก็ได้และส่วนใหญ่จะได้ผล บางครั้งมันอาจทำให้แอปของคุณออกจากระบบเนื่องจากคุณขัดจังหวะเธรด GUI อย่างมีประสิทธิภาพในขณะที่กำลังทำอย่างอื่น นี่คือข้อยกเว้นแบบไขว้ - ลองนึกภาพว่าพยายามอัปเดตกล่องข้อความในขณะที่ GUI กำลังวาดภาพอย่างอื่น

  • การดำเนินการใดให้ความสำคัญ
  • เป็นไปได้ไหมที่ทั้งสองจะเกิดขึ้นพร้อมกัน?
  • เกิดอะไรขึ้นกับคำสั่งอื่น ๆ ทั้งหมดที่ GUI ต้องใช้

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

เพื่อให้เข้าใจถึงสิ่งที่เกิดขึ้นจริงเบื้องหลังและความหมายของ "GUI Thread" การทำความเข้าใจว่า Message Pump หรือ Message Loop คืออะไร

คำตอบนี้มีคำตอบอยู่แล้วในคำถาม " ปั๊มข้อความคืออะไร " และขอแนะนำให้อ่านเพื่อทำความเข้าใจกลไกจริงที่คุณคาดหวังเมื่อโต้ตอบกับตัวควบคุม

การอ่านอื่น ๆ ที่คุณอาจพบว่ามีประโยชน์ ได้แก่ :

เกิดอะไรขึ้นกับ Begin Invoke

กฎสำคัญประการหนึ่งของการเขียนโปรแกรม Windows GUI คือเฉพาะเธรดที่สร้างตัวควบคุมเท่านั้นที่สามารถเข้าถึงและ / หรือแก้ไขเนื้อหาได้ (ยกเว้นข้อยกเว้นที่มีการจัดทำเป็นเอกสารบางส่วน) ลองทำจากเธรดอื่น ๆ และคุณจะได้รับพฤติกรรมที่คาดเดาไม่ได้ตั้งแต่การหยุดชะงักไปจนถึงข้อยกเว้นไปจนถึง UI ที่อัปเดตครึ่งหนึ่ง วิธีที่ถูกต้องในการอัปเดตตัวควบคุมจากเธรดอื่นคือการโพสต์ข้อความที่เหมาะสมไปยังคิวข้อความของแอปพลิเคชัน เมื่อปั๊มข้อความดำเนินการกับข้อความนั้นตัวควบคุมจะได้รับการอัปเดตบนเธรดเดียวกับที่สร้างขึ้น (โปรดจำไว้ว่าปั๊มข้อความจะทำงานบนเธรดหลัก)

และสำหรับภาพรวมโค้ดที่หนักหน่วงพร้อมตัวอย่างตัวแทน:

การดำเนินการข้ามเธรดไม่ถูกต้อง

// the canonical form (C# consumer)

public delegate void ControlStringConsumer(Control control, string text);  // defines a delegate type

public void SetText(Control control, string text) {
    if (control.InvokeRequired) {
        control.Invoke(new ControlStringConsumer(SetText), new object[]{control, text});  // invoking itself
    } else {
        control.Text=text;      // the "functional part", executing only on the main thread
    }
}

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

นอกจากนี้ยังมีการเขียนเพิ่มเติมถึงสิ่งที่เกิดขึ้นในอดีตที่อาจเป็นที่สนใจ


68

การควบคุมหรือหน้าต่างวัตถุในแบบฟอร์ม Windows เป็นเพียงห่อหุ้มรอบหน้าต่าง Win32 ระบุที่จับ (บางครั้งเรียก HWND) สิ่งต่างๆส่วนใหญ่ที่คุณทำกับตัวควบคุมจะส่งผลให้เกิดการเรียกใช้ Win32 API ที่ใช้หมายเลขอ้างอิงนี้ ที่จับเป็นของเธรดที่สร้างขึ้น (โดยทั่วไปคือเธรดหลัก) และไม่ควรถูกจัดการโดยเธรดอื่น หากมีเหตุผลบางอย่างที่คุณจำเป็นต้องดำเนินการบางอย่างโดยใช้การควบคุมจากเธรดอื่นคุณสามารถใช้Invokeเพื่อขอให้เธรดหลักดำเนินการในนามของคุณได้

ตัวอย่างเช่นหากคุณต้องการเปลี่ยนข้อความของป้ายกำกับจากเธรดผู้ปฏิบัติงานคุณสามารถดำเนินการดังนี้:

theLabel.Invoke(new Action(() => theLabel.Text = "hello world from worker thread!"));

คุณอธิบายได้ไหมว่าทำไมบางคนถึงทำอะไรแบบนี้? this.Invoke(() => this.Enabled = true);สิ่งที่thisอ้างถึงก็แน่นอนในเธรดปัจจุบันใช่มั้ย?
Kyle Delaney

1
@KyleDelaney วัตถุไม่ได้ "อยู่ใน" เธรดและเธรดปัจจุบันไม่จำเป็นต้องเป็นเธรดที่สร้างวัตถุ
Thomas Levesque

24

ถ้าคุณต้องการแก้ไขตัวควบคุมจะต้องทำในเธรดที่สร้างตัวควบคุม Invokeวิธีนี้ช่วยให้คุณสามารถเรียกใช้เมธอดในเธรดที่เกี่ยวข้อง (เธรดที่เป็นเจ้าของแฮนเดิลหน้าต่างพื้นฐานของคอนโทรล)

ในตัวอย่างด้านล่าง thread1 แสดงข้อยกเว้นเนื่องจาก SetText1 พยายามแก้ไข textBox1.Text จากเธรดอื่น แต่ใน thread2 การดำเนินการใน SetText2 จะดำเนินการในเธรดที่สร้างกล่องข้อความ

private void btn_Click(object sender, EvenetArgs e)
{
    var thread1 = new Thread(SetText1);
    var thread2 = new Thread(SetText2);
    thread1.Start();
    thread2.Start();
}

private void SetText1() 
{
    textBox1.Text = "Test";
}

private void SetText2() 
{
    textBox1.Invoke(new Action(() => textBox1.Text = "Test"));
}

ฉันชอบวิธีนี้มากมันซ่อนธรรมชาติ แต่ก็เป็นทางลัดที่ดีอยู่ดี
shytikov

7
Invoke((MethodInvoker)delegate{ textBox1.Text = "Test"; });

การใช้ System.Action ที่ผู้คนแนะนำเกี่ยวกับการตอบสนองอื่น ๆ ใช้งานได้กับเฟรมเวิร์ก 3.5+ เท่านั้นสำหรับเวอร์ชันเก่าสิ่งนี้ใช้งานได้ดี
Suicide Platypus

2

ในทางปฏิบัติหมายความว่าผู้ร่วมประชุมได้รับการรับรองว่าจะถูกเรียกใช้ในเธรดหลัก สิ่งนี้มีความสำคัญเนื่องจากในกรณีของ windows ควบคุมหากคุณไม่อัปเดตคุณสมบัติบนเธรดหลักคุณจะไม่เห็นการเปลี่ยนแปลงหรือตัวควบคุมจะมีข้อยกเว้น

รูปแบบคือ:

void OnEvent(object sender, EventArgs e)
{
   if (this.InvokeRequired)
   {
       this.Invoke(() => this.OnEvent(sender, e);
       return;
   }

   // do stuff (now you know you are on the main thread)
}

2

this.Invoke(delegate)ตรวจสอบให้แน่ใจว่าคุณกำลังเรียกผู้รับมอบสิทธิ์อาร์กิวเมนต์ไปthis.Invoke()ที่เธรดหลัก / เธรดที่สร้างขึ้น

ฉันสามารถพูดได้ว่ากฎ Thumb ไม่สามารถเข้าถึงการควบคุมแบบฟอร์มของคุณยกเว้นจากเธรดหลัก

บรรทัดต่อไปนี้อาจเหมาะสมสำหรับการใช้ Invoke ()

    private void SetText(string text)
    {
        // InvokeRequired required compares the thread ID of the
        // calling thread to the thread ID of the creating thread.
        // If these threads are different, it returns true.
        if (this.textBox1.InvokeRequired)
        {   
            SetTextCallback d = new SetTextCallback(SetText);
            this.Invoke(d, new object[] { text });
        }
        else
        {
            this.textBox1.Text = text;
        }
    }

มีสถานการณ์แม้ว่าคุณจะสร้างเธรด Threadpool (เช่นเธรดผู้ปฏิบัติงาน) ซึ่งจะทำงานบนเธรดหลัก จะไม่สร้างเธรดหลักเธรดใหม่เนื่องจากเธรดหลักพร้อมใช้งานสำหรับการประมวลผลคำแนะนำเพิ่มเติม ดังนั้นก่อนอื่นให้ตรวจสอบว่าเธรดที่ทำงานอยู่ในปัจจุบันเป็นเธรดหลักหรือไม่โดยใช้this.InvokeRequiredif คืนค่าเป็นจริงรหัสปัจจุบันกำลังทำงานบนเธรดของผู้ปฏิบัติงานดังนั้นจึงเรียกสิ่งนี้ Invoke (d, new object [] {text});

อื่นอัปเดตการควบคุม UI โดยตรง (ที่นี่คุณรับประกันได้ว่าคุณกำลังรันโค้ดบนเธรดหลัก)


1

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

ประเด็นสำคัญคือคุณจะเรียกเมธอดนี้จากเธรดผู้ปฏิบัติงานเพื่อให้คุณสามารถเข้าถึง UI ได้ (เพื่อเปลี่ยนค่าในเลเบล ฯลฯ ) เนื่องจากคุณไม่ได้รับอนุญาตให้ทำเช่นนั้นจากเธรดอื่นที่ไม่ใช่เธรด UI


0

ผู้รับมอบสิทธิ์เป็นแบบอินไลน์ActionหรือFunc<T>. คุณสามารถประกาศผู้รับมอบสิทธิ์นอกขอบเขตของวิธีการที่คุณกำลังเรียกใช้หรือใช้lambdaนิพจน์ ( =>); เนื่องจากคุณรันผู้รับมอบสิทธิ์ภายในเมธอดคุณจึงรันบนเธรดที่กำลังรันสำหรับหน้าต่าง / แอพพลิเคชั่นปัจจุบันซึ่งบิตเป็นตัวหนา

ตัวอย่าง Lambda

int AddFiveToNumber(int number)
{
  var d = (int i => i + 5);
  d.Invoke(number);
}

0

หมายความว่าผู้รับมอบสิทธิ์ที่คุณส่งผ่านถูกดำเนินการบนเธรดที่สร้างอ็อบเจ็กต์ Control (ซึ่งเป็นเธรด UI)

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

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