[Transfer] Summary of common methods for updating UI controls across threads in C# Winform

overview

In C# Winform programming, it is incorrect to directly update UI controls across threads, and the exception of "Inter-thread operations are invalid: accessing it from a thread other than the thread that created the control" often occurs. There are four commonly used methods for updating Winform UI controls across threads:
1. Update through the Post/Send method of the SynchronizationContext of the UI thread;
2. Update through the Invoke/BeginInvoke method of the UI control;

3. Use BackgroundWorker instead of Thread to perform asynchronous operations;
4. By setting window properties and canceling thread safety checks to avoid "cross-thread operation exceptions" (not thread-safe, it is recommended not to use).
In the following, the application of the above three methods will be illustrated with examples, hoping to be helpful to students who are new to C# Winform.

text

1. Update via the Post/Send method of the SynchronizationContext of the UI thread

usage: 

//共分三步
        //第一步:获取UI线程同步上下文(在窗体构造函数或FormLoad事件中)
        /// <summary>
        /// UI线程的同步上下文
        /// </summary>
        SynchronizationContext m_SyncContext = null;
        public Form1()
        {
            InitializeComponent();
            //获取UI线程同步上下文
            m_SyncContext = SynchronizationContext.Current;
            //Control.CheckForIllegalCrossThreadCalls = false;
        }
        //第二步:定义线程的主体方法
        /// <summary>
        /// 线程的主体方法
        /// </summary>
        private void ThreadProcSafePost()
        {
            //...执行线程任务

            //在线程中更新UI(通过UI线程同步上下文m_SyncContext)
            m_SyncContext.Post(SetTextSafePost, "This text was set safely by SynchronizationContext-Post.");

            //...执行线程其他任务
        }
        //第三步:定义更新UI控件的方法
        /// <summary>
        /// 更新文本框内容的方法
        /// </summary>
        /// <param name="text"></param>
        private void SetTextSafePost(object text)
        {
            this.textBox1.Text = text.ToString();
        }
        //之后,启动线程
        /// <summary>
        /// 启动线程按钮事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void setSafePostBtn_Click(object sender, EventArgs e)
        {
            this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafePost));
            this.demoThread.Start();
        }

Note: The three bold parts are the key. The main principle of this method is: during thread execution, the data that needs to be updated on the UI control is no longer directly updated, but the data is sent to the UI in the form of asynchronous/synchronous messages through the Post/Send method of the UI thread context The thread's message queue; after the UI thread receives the message, it decides to call the SetTextSafePost method in an asynchronous/synchronous manner to directly update its own controls according to whether the message is an asynchronous message or a synchronous message.

In essence, the message sent to the UI thread is not simple data, but a delegate call command.

//Update the UI in the thread (through the UI thread synchronization context m_SyncContext)
m_SyncContext.Post ( SetTextSafePost , "This text was set safely by SynchronizationContext-Post.");
This line of code can be interpreted as follows: to the synchronization context of the UI thread (m_SyncContext ) to submit an asynchronous message (UI thread, you execute the delegate asynchronously after receiving the message, call the method SetTextSafePost , the parameter is "this text was....").

2. Update through the Invoke/BeginInvoke method of the UI control

Usage: Similar to method 1, it can be divided into three steps.

// 共分三步

        // 第一步:定义委托类型
        // 将text更新的界面控件的委托类型
        delegate void SetTextCallback(string text);

        //第二步:定义线程的主体方法
        /// <summary>
        /// 线程的主体方法
        /// </summary>
        private void ThreadProcSafe()
        {
            //...执行线程任务

            //在线程中更新UI(通过控件的.Invoke方法)
            this.SetText("This text was set safely.");

            //...执行线程其他任务
        }
        //第三步:定义更新UI控件的方法
        /// <summary>
        /// 更新文本框内容的方法
        /// </summary>
        /// <param name="text"></param>
        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)//如果调用控件的线程和创建创建控件的线程不是同一个则为True
            {
                while (!this.textBox1.IsHandleCreated)
                {
                    //解决窗体关闭时出现“访问已释放句柄“的异常
                    if (this.textBox1.Disposing || this.textBox1.IsDisposed)
                        return;
                }
                SetTextCallback d = new SetTextCallback(SetText);
                this.textBox1.Invoke(d, new object[] { text });
            }
            else
            {
                this.textBox1.Text = text;
            }
        }
        //之后,启动线程
        /// <summary>
        /// 启动线程按钮事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void setTextSafeBtn_Click(
            object sender,
            EventArgs e)
        {
            this.demoThread =
                new Thread(new ThreadStart(this.ThreadProcSafe));

            this.demoThread.Start();
        }

Description: This method is currently the mainstream method used to update the UI across threads. Use the control's Invoke/BeginInvoke method to transfer the delegate to the UI thread for invocation to achieve thread-safe updates. The principle is similar to that of method 1. In essence, the message to be submitted in the thread is called through the control handle and delegated to the UI thread for processing.

To solve the abnormal part of "accessing the released handle" when the form is closed, refer to the article of Blog Garden-Affair Classmate .

3.Use BackgroundWorker instead of Thread to perform asynchronous operations

usage:

//共分三步

        //第一步:定义BackgroundWorker对象,并注册事件(执行线程主体、执行UI更新事件)
        private BackgroundWorker backgroundWorker1 =null;
        public Form1()
        {
            InitializeComponent();
           
            backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
            //设置报告进度更新
            backgroundWorker1.WorkerReportsProgress = true;
            //注册线程主体方法
            backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
            //注册更新UI方法
            backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
            //backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
        }

        //第二步:定义执行线程主体事件
        //线程主体方法
        public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            //...执行线程任务

            //在线程中更新UI(通过ReportProgress方法)
            backgroundWorker1.ReportProgress(50, "This text was set safely by BackgroundWorker.");

            //...执行线程其他任务
        }
        //第三步:定义执行UI更新事件
        //UI更新方法
        public void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.textBox1.Text = e.UserState.ToString();
        }
        //之后,启动线程
        //启动backgroundWorker
        private void setTextBackgroundWorkerBtn_Click(object sender, EventArgs e)
        {
            this.backgroundWorker1.RunWorkerAsync();
        }

Description: BackgroundWorker is a good choice when performing asynchronous tasks in C# Winform. It is the product of the idea of ​​EAP (Event based Asynchronous Pattern). DoWork is used to execute asynchronous tasks. During/after task execution, we can perform thread-safe UI updates through the ProgressChanged and ProgressCompleted events.

It should be noted that: //Set report progress update
            backgroundWorker1.WorkerReportsProgress = true;
By default, BackgroundWorker does not report progress, you need to display and set report progress properties.

4. By setting the form properties and canceling the thread safety check to avoid "inter-thread operation invalid exception" (non-thread safe, it is recommended not to use)

Usage: Set the static property CheckForIllegalCrossThreadCalls of the Control class to false.

public Form1()
        {
            InitializeComponent();
            //指定不再捕获对错误线程的调用
            Control.CheckForIllegalCrossThreadCalls = false;
        }

Description: By setting the CheckForIllegalCrossThreadCalls property, you can indicate whether to catch unsafe operation exceptions between threads. The value of this attribute defaults to true, that is, unsafe operations between threads are to catch exceptions ("Inter-thread operations are invalid" exceptions). This exception is simply blocked by setting this property to false. The annotation of Control.CheckForIllegalCrossThreadCalls is as follows:

//
        // 摘要:
        //     获取或设置一个值,该值指示是否捕获对错误线程的调用,这些调用在调试应用程序时访问控件的 System.Windows.Forms.Control.Handle
        //     属性。
        //
        // 返回结果:
        //     如果捕获了对错误线程的调用,则为 true;否则为 false。
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [SRDescription("ControlCheckForIllegalCrossThreadCalls")]
        [Browsable(false)]
        public static bool CheckForIllegalCrossThreadCalls { get; set; }

Summary:

Of the four methods introduced in this article, the first three are thread-safe and can be used in actual projects according to local conditions. The last method is not thread-safe, and beginners can experience it experimentally but it is not recommended to use it.

The following list compares these four methods 

method thread safety Asynchronous/synchronous support other
UI Sync Context updates yes Post/Send Try to get the synchronization context in the form constructor and FormLoad
Control Invoke yes control.Invoke/BeginInvoke Pay attention to check whether the control handle has been released
Update BackgroundWorker yes

ProgressChanged、RunWorkerCompleted

Event synchronization update
report progress

CheckForIllegalCrossThreadCalls

Cancel the cross-thread call check
no Synchronization Update simple, not recommended

References:

Jeffrey Richter, "clr var C#"

MSDN, How to: Make Thread-Safe Calls to Windows Forms Controls

Reason, summary of safe access control in C# thread (reuse delegate, avoid complicated delegate, Invoke)

Original link:

Summary of common methods for updating UI controls across threads in C# Winform

Guess you like

Origin blog.csdn.net/weixin_42099527/article/details/124622506