การดำเนินการข้ามเธรดไม่ถูกต้อง: การควบคุมการเข้าถึงจากเธรดอื่นที่ไม่ใช่เธรดที่สร้างขึ้น


584

ฉันมีสถานการณ์ (แบบฟอร์ม Windows, C #, .NET)

  1. มีรูปแบบหลักที่โฮสต์การควบคุมผู้ใช้บางส่วน
  2. การควบคุมผู้ใช้ดำเนินการกับข้อมูลจำนวนมากเช่นถ้าฉันเรียกUserControl_LoadวิธีการโดยตรงUI จะไม่ตอบสนองต่อช่วงเวลาสำหรับการดำเนินการตามวิธีโหลด
  3. เพื่อเอาชนะสิ่งนี้ฉันโหลดข้อมูลในเธรดอื่น (พยายามเปลี่ยนรหัสที่มีอยู่ให้น้อยที่สุดเท่าที่จะทำได้)
  4. ฉันใช้เธรดผู้ทำงานเบื้องหลังซึ่งจะโหลดข้อมูลและเมื่อเสร็จแล้วจะแจ้งให้แอปพลิเคชันทราบว่าได้ทำงานเสร็จแล้ว
  5. ตอนนี้เป็นปัญหาจริง UI ทั้งหมด (ฟอร์มหลักและส่วนควบคุมย่อยผู้ใช้) ถูกสร้างขึ้นบนเธรดหลักหลัก ในวิธีโหลดของการควบคุมผู้ใช้ฉันกำลังดึงข้อมูลตามค่าของการควบคุมบางอย่าง (เช่นกล่องข้อความ) ใน userControl

รหัสเทียมจะมีลักษณะเช่นนี้:

รหัส 1

UserContrl1_LoadDataMethod()
{
    if (textbox1.text == "MyName") // This gives exception
    {
        //Load data corresponding to "MyName".
        //Populate a globale variable List<string> which will be binded to grid at some later stage.
    }
}

ข้อยกเว้นที่ให้ไว้คือ

การดำเนินการข้ามเธรดไม่ถูกต้อง: การควบคุมการเข้าถึงจากเธรดอื่นที่ไม่ใช่เธรดที่สร้างขึ้น

หากต้องการทราบข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ฉันได้ทำ Google และมีข้อเสนอแนะเกิดขึ้นเช่นใช้รหัสต่อไปนี้

รหัส 2

UserContrl1_LoadDataMethod()
{
    if (InvokeRequired) // Line #1
    {
        this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
        return;
    }

    if (textbox1.text == "MyName") // Now it wont give an exception
    {
    //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be binded to grid at some later stage
    }
}

แต่ ... แต่ดูเหมือนว่าฉันจะกลับไปที่หนึ่งตาราง แอปพลิเคชันไม่ตอบสนองอีกครั้ง ดูเหมือนว่าจะเกิดจากการดำเนินการของบรรทัด # 1 หากเงื่อนไข งานโหลดเสร็จแล้วอีกครั้งโดยผู้ปกครองด้ายและไม่ใช่ที่สามที่ฉันวางไข่

ฉันไม่รู้ว่าฉันเข้าใจถูกหรือผิด ฉันใหม่เพื่อเธรด

ฉันจะแก้ไขปัญหานี้และอะไรคือผลของการใช้งาน Line # 1 if block

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

ดังนั้นการเข้าถึงค่าเพื่อให้สามารถดึงข้อมูลที่เกี่ยวข้องจากฐานข้อมูล


สำหรับอินสแตนซ์เฉพาะของฉันของข้อผิดพลาดนี้ฉันพบวิธีแก้ปัญหาเพื่อใช้ BackgroundWorker ในแบบฟอร์มเพื่อจัดการส่วนที่ต้องใช้ข้อมูลจำนวนมากของรหัส (เช่นใส่รหัสปัญหาทั้งหมดลงใน backgroundWorker1_DoWork () วิธีการและเรียกมันผ่าน backgroundWorker1.RunWorkerAsync ()) ... แหล่งที่มาทั้งสองนี้ชี้ให้ฉันเห็นทิศทางที่ถูกต้อง: stackoverflow.com/questions/4806742/… youtube.com/ watch? v = MLrrbG6V1zM
Giollia

คำตอบ:


433

ตามความคิดเห็นอัปเดตของ Prerak K (ตั้งแต่ลบไปแล้ว):

ฉันเดาว่าฉันไม่ได้นำเสนอคำถามอย่างถูกต้อง

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

ดังนั้นการเข้าถึงค่าเพื่อให้สามารถดึงข้อมูลที่เกี่ยวข้องจากฐานข้อมูล

โซลูชันที่คุณต้องการควรมีลักษณะดังนี้:

UserContrl1_LOadDataMethod()
{
    string name = "";
    if(textbox1.InvokeRequired)
    {
        textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
    }
    if(name == "MyName")
    {
        // do whatever
    }
}

ทำการประมวลผลที่ร้ายแรงของคุณในเธรดแยกต่างหากก่อนที่คุณพยายามสลับกลับไปยังเธรดของตัวควบคุม ตัวอย่างเช่น:

UserContrl1_LOadDataMethod()
{
    if(textbox1.text=="MyName") //<<======Now it wont give exception**
    {
        //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be
        //bound to grid at some later stage
        if(InvokeRequired)
        {
            // after we've done all the processing, 
            this.Invoke(new MethodInvoker(delegate {
                // load the control with the appropriate data
            }));
            return;
        }
    }
}

1
เป็นเวลานานแล้วที่ฉันได้เขียนโปรแกรม C # แต่จากบทความ MSDN และความรู้ที่เป็นหย่อม ๆ ของฉันดูเหมือนว่า
Jeff Hubbard

1
ความแตกต่างคือ BeginInvoke () เป็นแบบอะซิงโครนัสในขณะที่ Invoke () ทำงานพร้อมกัน stackoverflow.com/questions/229554/…
frzsombor

178

แบบจำลองเธรดใน UI

โปรดอ่านThreading Modelในแอปพลิเคชัน UI เพื่อทำความเข้าใจแนวคิดพื้นฐาน ลิงก์นำทางไปยังเพจที่อธิบายโมเดลการทำเธรด WPF อย่างไรก็ตาม Windows Forms ใช้แนวคิดเดียวกัน

เธรด UI

  • มีเธรดเดียวเท่านั้น (เธรด UI) ที่ได้รับอนุญาตให้เข้าถึงSystem.Windows.Forms.Controlและสมาชิกคลาสย่อย
  • ความพยายามในการเข้าถึงสมาชิกของSystem.Windows.Forms.Controlจากเธรดที่แตกต่างจากเธรด UI จะทำให้เกิดข้อยกเว้นข้ามเธรด
  • เนื่องจากมีเพียงเธรดเดียวการดำเนินการ UI ทั้งหมดจะถูกจัดคิวเป็นรายการงานในเธรดนั้น

ป้อนคำอธิบายรูปภาพที่นี่

  • หากไม่มีการทำงานสำหรับเธรด UI แสดงว่ามีช่องว่างที่ไม่ได้ใช้งานซึ่งสามารถใช้งานได้โดยการคำนวณที่ไม่เกี่ยวกับ UI
  • เพื่อที่จะใช้ช่องว่างที่กล่าวถึงให้ใช้System.Windows.Forms.Control.InvokeหรือSystem.Windows.Forms.Control.BeginInvokeวิธีการ:

ป้อนคำอธิบายรูปภาพที่นี่

BeginInvoke และเรียกใช้เมธอด

  • การคำนวณค่าโสหุ้ยของวิธีการที่เรียกควรมีขนาดเล็กเช่นเดียวกับการคำนวณค่าโสหุ้ยของวิธีการจัดการเหตุการณ์เพราะมีการใช้เธรด UI นั่นคือสิ่งเดียวกันที่รับผิดชอบการจัดการอินพุตของผู้ใช้ โดยไม่คำนึงถึงว่านี้เป็นSystem.Windows.Forms.Control.InvokeหรือSystem.Windows.Forms.Control.BeginInvoke
  • ในการคำนวณการดำเนินการที่มีราคาแพงให้ใช้เธรดแยกต่างหากเสมอ เนื่องจาก. NET 2.0 BackgroundWorkerมีความมุ่งมั่นในการดำเนินการคำนวณค่าใช้จ่ายใน Windows Forms อย่างไรก็ตามในการแก้ปัญหาใหม่ที่คุณควรจะใช้รูปแบบ async-รอคอยตามที่อธิบายไว้ที่นี่
  • ใช้System.Windows.Forms.Control.InvokeหรือSystem.Windows.Forms.Control.BeginInvokeวิธีการเท่านั้นเพื่อปรับปรุงส่วนติดต่อผู้ใช้ หากคุณใช้มันสำหรับการคำนวณหนักแอปพลิเคชันของคุณจะบล็อก:

ป้อนคำอธิบายรูปภาพที่นี่

วิงวอน

  • System.Windows.Forms.Control.Invokeทำให้เธรดแยกต่างหากรอจนกว่าเมธอดที่เรียกใช้เสร็จสิ้นแล้ว:

ป้อนคำอธิบายรูปภาพที่นี่

BeginInvoke

  • System.Windows.Forms.Control.BeginInvokeไม่ทำให้เธรดแยกต่างหากรอจนกว่าเมธอดที่เรียกใช้เสร็จสิ้นแล้ว:

ป้อนคำอธิบายรูปภาพที่นี่

โซลูชันรหัส

อ่านคำตอบสำหรับคำถามวิธีการอัปเดต GUI จากเธรดอื่นใน C # . สำหรับ C # 5.0 และ 4.5 .NET ทางออกที่แนะนำคือที่นี่


นี่คือการเชื่อมโยงการอัปเดตสำหรับรูปแบบเกลียว WPF
Adam Howell

72

คุณต้องการใช้InvokeหรือBeginInvokeสำหรับงานขั้นต่ำที่ต้องเปลี่ยน UI เท่านั้น วิธี "หนา" ของคุณควรดำเนินการในเธรดอื่น (เช่นผ่านBackgroundWorker) แต่ใช้Control.Invoke/ Control.BeginInvokeเพียงแค่อัปเดต UI วิธีนี้จะทำให้เธรด UI ของคุณมีอิสระในการจัดการกิจกรรม UI และอื่น ๆ

ดูบทความเกลียวของฉันสำหรับตัวอย่าง WinForms - แม้ว่าบทความจะถูกเขียนก่อนที่จะBackgroundWorkerมาถึงที่เกิดเหตุและฉันกลัวว่าฉันยังไม่ได้อัปเดตในแง่นั้น BackgroundWorkerทำให้การติดต่อกลับง่ายขึ้นเล็กน้อย


ที่นี่ในเงื่อนไขของฉันนี้ ฉันไม่ได้เปลี่ยน UI ฉันเพียงแค่เข้าถึงค่าปัจจุบันจากด้ายเด็ก ข้อเสนอแนะใด ๆ ที่จะนำไปใช้
Prerak K

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

สวัสดีจอนฉันเชื่อว่าคุณกำลังมุ่งหน้าฉันไปในทิศทางที่ถูกต้อง ใช่ฉันต้องการค่าโดยที่ฉันไม่สามารถดำเนินการต่อไป โปรดอธิบายเกี่ยวกับ 'การใช้ผู้รับมอบสิทธิ์ซึ่งคืนค่าให้' ขอบคุณ
Prerak K

1
ใช้ผู้รับมอบสิทธิ์เช่น Func <string>: string text = textbox1.Invoke ((Func <string>) () => textbox1.Text); (นั่นคือสมมติว่าคุณกำลังใช้ C # 3.0 - คุณสามารถใช้วิธีที่ไม่ระบุตัวตนเป็นอย่างอื่น)
Jon Skeet

45

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

Invoke(new Action(() =>
                {
                    label1.Text = "WooHoo!!!";
                }));

นี่คือวิธีที่ฉันเข้าถึงการควบคุมฟอร์มใด ๆ จากเธรด


1
Invoke or BeginInvoke cannot be called on a control until the window handle has been createdนี้จะช่วยให้ฉัน ฉันแก้ไขได้ที่นี่
rupweb

42

ฉันมีปัญหากับFileSystemWatcherและพบว่ารหัสต่อไปนี้แก้ไขปัญหา:

fsw.SynchronizingObject = this

ตัวควบคุมใช้วัตถุฟอร์มปัจจุบันเพื่อจัดการกับเหตุการณ์และดังนั้นจึงจะอยู่ในเธรดเดียวกัน


2
สิ่งนี้บันทึกเบคอนของฉัน ใน VB.NET ฉันใช้.SynchronizingObject = Me
codingcoding

20

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

public static class Extensions
{
    public static void Invoke<TControlType>(this TControlType control, Action<TControlType> del) 
        where TControlType : Control
        {
            if (control.InvokeRequired)
                control.Invoke(new Action(() => del(control)));
            else
                del(control);
    }
}

และจากนั้นคุณสามารถทำสิ่งนี้:

textbox1.Invoke(t => t.Text = "A");

ไม่เลอะเทอะไปไหน - ง่าย


't' คืออะไรที่นี่
Rawat

@Rawat tในกรณีนี้จะเป็นtextbox1- จะถูกส่งเป็นอาร์กิวเมนต์
Rob

17

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

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


15

วิธีการแก้ปัญหาที่สะอาดที่สุด (และเหมาะสม) สำหรับปัญหาการข้ามเธรด UI คือการใช้ SynchronizationContext ดูการซิงโครไนซ์การเรียกไปยัง UI ในบทความแอปพลิเคชันแบบมัลติเธรดมันอธิบายได้เป็นอย่างดี


10

รูปลักษณ์ใหม่โดยใช้ Async / Await และ callback คุณต้องการโค้ดเพียงบรรทัดเดียวถ้าคุณใช้วิธีการขยายในโครงการของคุณ

/// <summary>
/// A new way to use Tasks for Asynchronous calls
/// </summary>
public class Example
{
    /// <summary>
    /// No more delegates, background workers etc. just one line of code as shown below
    /// Note it is dependent on the XTask class shown next.
    /// </summary>
    public async void ExampleMethod()
    {
        //Still on GUI/Original Thread here
        //Do your updates before the next line of code
        await XTask.RunAsync(() =>
        {
            //Running an asynchronous task here
            //Cannot update GUI Thread here, but can do lots of work
        });
        //Can update GUI/Original thread on this line
    }
}

/// <summary>
/// A class containing extension methods for the Task class 
/// Put this file in folder named Extensions
/// Use prefix of X for the class it Extends
/// </summary>
public static class XTask
{
    /// <summary>
    /// RunAsync is an extension method that encapsulates the Task.Run using a callback
    /// </summary>
    /// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
    /// <returns></returns>
    public async static Task RunAsync(Action Code)
    {
        await Task.Run(() =>
        {
            Code();
        });
        return;
    }
}

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

การเพิ่มลองจับการบันทึกข้อยกเว้นอัตโนมัติและ CallBack

    /// <summary>
    /// Run Async
    /// </summary>
    /// <typeparam name="T">The type to return</typeparam>
    /// <param name="Code">The callback to the code</param>
    /// <param name="Error">The handled and logged exception if one occurs</param>
    /// <returns>The type expected as a competed task</returns>

    public async static Task<T> RunAsync<T>(Func<string,T> Code, Action<Exception> Error)
    {
       var done =  await Task<T>.Run(() =>
        {
            T result = default(T);
            try
            {
               result = Code("Code Here");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unhandled Exception: " + ex.Message);
                Console.WriteLine(ex.StackTrace);
                Error(ex);
            }
            return result;

        });
        return done;
    }
    public async void HowToUse()
    {
       //We now inject the type we want the async routine to return!
       var result =  await RunAsync<bool>((code) => {
           //write code here, all exceptions are logged via the wrapped try catch.
           //return what is needed
           return someBoolValue;
       }, 
       error => {

          //exceptions are already handled but are sent back here for further processing
       });
        if (result)
        {
            //we can now process the result because the code above awaited for the completion before
            //moving to this statement
        }
    }

10

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

CheckForIllegalCrossThreadCalls = false

ในตัวForm1()สร้าง


9

ทำตามวิธีที่ง่ายที่สุด (ในความคิดของฉัน) เพื่อปรับเปลี่ยนวัตถุจากหัวข้ออื่น:

using System.Threading.Tasks;
using System.Threading;

namespace TESTE
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Action<string> DelegateTeste_ModifyText = THREAD_MOD;
            Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD");
        }

        private void THREAD_MOD(string teste)
        {
            textBox1.Text = teste;
        }
    }
}

ง่าย! ขอบคุณ
Ali Esmaeili


7

ฉันพบความจำเป็นในการทำสิ่งนี้ในขณะที่เขียนโปรแกรมควบคุม monotouch บน iOS-Phone ในโครงการต้นแบบ Visual Studio winforms นอก xamarin stuidio ชอบที่จะเขียนโปรแกรมใน VS บน xamarin studio ให้มากที่สุดเท่าที่จะเป็นไปได้ฉันต้องการให้คอนโทรลเลอร์แยกตัวออกจากกรอบโทรศัพท์อย่างสมบูรณ์ วิธีนี้จะนำไปใช้กับกรอบงานอื่น ๆ เช่น Android และ Windows Phone จะง่ายขึ้นสำหรับการใช้งานในอนาคต

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

public partial class Form1 : Form
{
    private ExampleController.MyController controller;

    public Form1()
    {          
        InitializeComponent();
        controller = new ExampleController.MyController((ISynchronizeInvoke) this);
        controller.Finished += controller_Finished;
    }

    void controller_Finished(string returnValue)
    {
        label1.Text = returnValue; 
    }

    private void button1_Click(object sender, EventArgs e)
    {
        controller.SubmitTask("Do It");
    }
}

แบบฟอร์ม GUI ไม่ทราบว่าตัวควบคุมกำลังทำงานแบบอะซิงโครนัส

public delegate void FinishedTasksHandler(string returnValue);

public class MyController
{
    private ISynchronizeInvoke _syn; 
    public MyController(ISynchronizeInvoke syn) {  _syn = syn; } 
    public event FinishedTasksHandler Finished; 

    public void SubmitTask(string someValue)
    {
        System.Threading.ThreadPool.QueueUserWorkItem(state => submitTask(someValue));
    }

    private void submitTask(string someValue)
    {
        someValue = someValue + " " + DateTime.Now.ToString();
        System.Threading.Thread.Sleep(5000);
//Finished(someValue); This causes cross threading error if called like this.

        if (Finished != null)
        {
            if (_syn.InvokeRequired)
            {
                _syn.Invoke(Finished, new object[] { someValue });
            }
            else
            {
                Finished(someValue);
            }
        }
    }
}

6

นี่เป็นอีกทางเลือกหนึ่งหากวัตถุที่คุณกำลังทำงานไม่มี

(InvokeRequired)

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

delegate void updateMainFormObject(FormObjectType objectWithoutInvoke, string text);

private void updateFormObjectType(FormObjectType objectWithoutInvoke, string text)
{
    MainForm.Invoke(new updateMainFormObject(UpdateObject), objectWithoutInvoke, text);
}

public void UpdateObject(ToolStripStatusLabel objectWithoutInvoke, string text)
{
    objectWithoutInvoke.Text = text;
}

มันทำงานเหมือนข้างต้น แต่มันเป็นวิธีการที่แตกต่างกันถ้าคุณไม่มีวัตถุที่มีการร้องขอ แต่จะมีการเข้าถึง MainForm


5

ตามบรรทัดเดียวกันกับคำตอบก่อนหน้านี้ แต่เพิ่มสั้นมากที่อนุญาตให้ใช้คุณสมบัติการควบคุมทั้งหมดโดยไม่มีข้อยกเว้นการข้ามเธรด

วิธีการช่วยเหลือ

/// <summary>
/// Helper method to determin if invoke required, if so will rerun method on correct thread.
/// if not do nothing.
/// </summary>
/// <param name="c">Control that might require invoking</param>
/// <param name="a">action to preform on control thread if so.</param>
/// <returns>true if invoke required</returns>
public bool ControlInvokeRequired(Control c, Action a)
{
    if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate
    {
        a();
    }));
    else return false;

    return true;
}

ตัวอย่างการใช้งาน

// usage on textbox
public void UpdateTextBox1(String text)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return;
    textBox1.Text = ellapsed;
}

//Or any control
public void UpdateControl(Color c, String s)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(myControl, () => UpdateControl(c, s))) return;
    myControl.Text = s;
    myControl.BackColor = c;
}


5

ตัวอย่างเช่นการรับข้อความจากส่วนควบคุมของเธรด UI:

Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String

Private Function GetControlText(ByVal ctl As Control) As String
    Dim text As String

    If ctl.InvokeRequired Then
        text = CStr(ctl.Invoke(
            New GetControlTextInvoker(AddressOf GetControlText), ctl))
    Else
        text = ctl.Text
    End If

    Return text
End Function

3

คำถามเดียวกัน: how-to-update-the-gui-from-another-thread-in-c

สองทาง:

  1. ส่งคืนค่าใน e.result และใช้เพื่อตั้งค่ากล่องข้อความ yout ใน backgroundWorker_RunWorkerCompleted เหตุการณ์

  2. ประกาศตัวแปรบางอย่างเพื่อเก็บค่าเหล่านี้ในคลาสที่แยกต่างหาก (ซึ่งจะทำงานในฐานะผู้ถือข้อมูล) สร้างอินสแตนซ์คงที่ของคลาสนี้และคุณสามารถเข้าถึงได้บนทุกเธรด

ตัวอย่าง:

public  class data_holder_for_controls
{
    //it will hold value for your label
    public  string status = string.Empty;
}

class Demo
{
    public static  data_holder_for_controls d1 = new data_holder_for_controls();
    static void Main(string[] args)
    {
        ThreadStart ts = new ThreadStart(perform_logic);
        Thread t1 = new Thread(ts);
        t1.Start();
        t1.Join();
        //your_label.Text=d1.status; --- can access it from any thread 
    }

    public static void perform_logic()
    {
        //put some code here in this function
        for (int i = 0; i < 10; i++)
        {
            //statements here
        }
        //set result in status variable
        d1.status = "Task done";
    }
}



0

วิธีที่ง่ายและใช้งานได้อีกครั้งเพื่อแก้ไขปัญหานี้

วิธีการขยาย

public static class FormExts
{
    public static void LoadOnUI(this Form frm, Action action)
    {
        if (frm.InvokeRequired) frm.Invoke(action);
        else action.Invoke();
    }
}

ตัวอย่างการใช้งาน

private void OnAnyEvent(object sender, EventArgs args)
{
    this.LoadOnUI(() =>
    {
        label1.Text = "";
        button1.Text = "";
    });
}

-3

มีสองตัวเลือกสำหรับการดำเนินการข้ามเธรด

Control.InvokeRequired Property 

และที่สองคือการใช้

SynchronizationContext Post Method

Control.InvokeRequired จะมีประโยชน์ก็ต่อเมื่อตัวควบคุมการทำงานที่สืบทอดมาจากคลาสควบคุมในขณะที่สามารถใช้งานได้ทุกที่ ข้อมูลที่เป็นประโยชน์บางอย่างมีลิงค์ดังต่อไปนี้

ข้ามการอัปเดต UI .สุทธิ

การข้ามเธรด UI ที่ใช้ SynchronizationContext | .สุทธิ

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