zoukankan      html  css  js  c++  java
  • 实现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

  • 相关阅读:
    django入门系列 -表单视图
    django学习 -模型层初体验
    python 元组与list的区别
    解决容器内部无法访问宿主机端口的问题
    记一次pyccharm Terminal django 执行 python migrate 无法生成新的数据表的报错过程
    ES学习之ES语法入门
    docker seleniumgrid 问题解决
    selenium的Grid方式遇到 1 requests waiting for a slot to be free. 问题
    js写的ajax
    ajax jQ写的上传进度条
  • 原文地址:https://www.cnblogs.com/asdyzh/p/9876873.html
Copyright © 2011-2022 走看看