實現Winform 跨線程安全訪問UI控件


  在多線程操作WinForm窗體上的控件時,出現“線程間操作無效:從不是創建控件XXXX的線程訪問它”,那是因為默認情況下,在Windows應用程序中,.NET Framework不允許在一個線程中直接操作另一個線程中的控件(因為訪問Windows窗體控件本質上不是線程安全的)。微軟為了線程安全,窗體上的控件只能通過創建控件的線程來操作控件的數據,也就是只能是UI線程來操作窗體上的控件!可看看Control的Invoke和BeginInvoke

  要解決這個問題可以用以下方法:

  1、關閉線程安全檢查(不過本人不推薦,這種方式可能會發生一些不可預計的后果)

Control對象.CheckForIllegalCrossThreadCalls = false;

  2、使用控件的Invoke方法(或BeginInvoke方法、BackgroundWorker)

(1)、Invoke方法

if (this.InvokeRequired)
{
    this.Invoke(new Action(() => button1.Enabled = false));
    //button1.Invoke(new MethodInvoker(delegate() { button1.Enabled = false; }));
//textBox1.SafeCall(() =>{ textBox1.Text = (i++).ToString();});
button1.Invoke(new MethodInvoker(() => button1.Enabled = false )); button1.Invoke(new Action(() => button1.Enabled = false)); // 跨線程訪問UI控件 } else { button1.Enabled = false }

(2)、BeginInvoke方法

        delegate void AppendValueDelegate(string strValue);

        public void AppendValue(string strValue)
        {
            textBox1.BeginInvoke(new AppendValueDelegate(AddValueToTextBox), new object[] { strValue });
        }

        private void AddValueToTextBox(string strValue)
        {
            textBox1.Text += strValue;
        }

可精簡成:

        public void AppendValue(string strValue)
        {
            // 無返回值無參數用MethodInvoker委托,無返回值可有參數用Action委托,有返回值可有參數用Func委托
            textBox1.BeginInvoke(new Action<string>(msg => textBox1.Text += msg), 
                new object[] { strValue });
        }

  3、使用委托

public delegate void AddLog(string info);
/// <summary>
/// 添加日志
/// </summary>
AddLog OnAddLog;

/// <summary>
/// 加載事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FrmDataBackup_Load(object sender, EventArgs e)
{
    OnAddLog = new AddLog(PrintMsg);
}

/// <summary>
/// 打印信息到即時顯示控件
/// </summary>
/// <param name="info"></param>
public void PrintMsg(string info)
{
    // InvokeRequired 屬性判斷是否跨線程操作
    if (this.InvokeRequired)
    {
        this.Invoke(OnAddLog, info);
        return;
    }
    listBoxInfo.Items.Insert(0, "[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] " + info);
    if (listBoxInfo.Items.Count > 100)
    {
        listBoxInfo.Items.RemoveAt(100);
    }
}

4、使用SynchronizationContext基類,該類記錄着線程的同步上下文對象,我們可以通過在GUI線程中調用SynchronizationContext.Current屬性來獲得GUI線程的同步上下文,然后當線程池線程需要更新窗體時,可以調用保存的SynchronizationContext派生對象的Post方法(Post方法會將回調函數送到GUI線程的隊列中,每個線程都有各自的操作隊列的,線程的執行都是從這個隊列中拿方法去執行),向Post方法傳遞要由GUI線程調用的方法(該方法的定義要匹配SendOrPostCallback委托的簽名),還需要想Post方法傳遞一個要傳給回調方法的參數。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.Remoting.Messaging;
using System.Threading;

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

        /// <summary>
        /// 定義用來實現異步編程的委托
         /// </summary>
        /// <returns></returns>
        private delegate string GetTextInfoDelegate();

        /// <summary>
        /// 線程同步上下文對象
         /// </summary>
        SynchronizationContext syncContext;
        /// <summary>
        /// 調用委托
         /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            GetTextInfoDelegate textInfo = new GetTextInfoDelegate(GetInfo);
            textInfo.BeginInvoke(new AsyncCallback(GetTextInfoResult), null);
            // 捕捉調用線程的同步上下文派生對象
              syncContext = SynchronizationContext.Current;
        }

        /// <summary>
        /// 異步操作獲取信息
         /// </summary>
        /// <returns></returns>
        private string GetInfo()
        {
            return "使用SynchronizationContext基類";
        }

        /// <summary>
        /// 異步操作完成時執行的方法
         /// </summary>
        /// <param name="result"></param>
        private void GetTextInfoResult(IAsyncResult result)
        {
            GetTextInfoDelegate caller = (GetTextInfoDelegate)((AsyncResult)result).AsyncDelegate;
            // 調用EndInvoke去等待異步調用完成並且獲得返回值
             // 如果異步調用尚未完成,則 EndInvoke 會一直阻止調用線程,直到異步調用完成
             string text = caller.EndInvoke(result);

            // 通過獲得GUI線程的同步上下文的派生對象,
             // 然后調用Post方法來使更新GUI操作方法由GUI 線程去執行
             // ShowText(text);   // 報錯:線程間操作無效: 從不是創建控件“textBox1”的線程訪問它。
             syncContext.Post(new SendOrPostCallback(ShowText), text); 
        }

        /// <summary>
        /// 顯示結果到TextBox文本框
         /// 因為該方法是由GUI線程執行的,所以當然就可以訪問窗體控件了
         /// </summary>
        /// <param name="text"></param>
        private void ShowText(object text)
        {
            textBox1.Text = text.ToString();
        }
    }
}

參考資料:http://www.cnblogs.com/easyfrog/archive/2012/02/08/2343075.html 和 http://www.cnblogs.com/zhili/archive/2013/05/10/APM.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM