• C#跨线程操作控件的最简单实现探究


    随着程序复杂度的提高,程序不可避免会出现多个线程,此时就很可能存在跨线程操作控件的问题。

    跨线程操作UI控件主要有三类方式:

    1、禁止系统的线程间操作检查。(此法不建议使用

    2、使用Invoke(同步)或者BeginInvoke(异步)。(使用委托实现,并用lambda表达式简化代码

    3、使用BackgroundWorker组件。(此法暂不介绍,详情可见文末的参考资料

    先看一个跨线程操作失败的例子:

    新建一个Winform窗口程序项目,拖一个button1和label1控件到Form1窗体上。启动程序以后试图通过点击button1改变label1的值,完整代码如下:

     1 using System;
     2 using System.Threading;
     3 using System.Windows.Forms;
     4 
     5 namespace Windows跨线程调用控件
     6 {
     7     public partial class Form1 : Form
     8     {
     9         public Form1()
    10         {
    11             InitializeComponent();
    12         }
    13 
    14         private void button1_Click(object sender, EventArgs e)
    15         {
    16             Thread thread1 = new Thread(new ParameterizedThreadStart(UpdateLabel));
    17             thread1.Start("label已更新");
    18         }
    19         
    20         private void UpdateLabel(object str)
    21         {
    22             label1.Text = str.ToString();
    23         }
    24     }
    25 }

    点击button1以后运行报错:

    解决方案:

    方法一:禁止系统的线程间操作检查。

    代码就一句话:Control.CheckForIllegalCrossThreadCalls = false;通常写在Form1类的构造方法Form1()中。如下所示:

    1 public Form1()
    2         {
    3             InitializeComponent();
    4 
    5             Control.CheckForIllegalCrossThreadCalls = false;         
    6         }

    但是,这种方法是很不可靠的,有时候还是会报错。

    方法二:使用Invoke(同步)或者BeginInvoke(异步)最精简的代码如下:

     1 using System;
     2 using System.Threading;
     3 using System.Windows.Forms;
     4 
     5 namespace Windows跨线程调用控件
     6 {
     7     public partial class Form1 : Form
     8     {
     9         public Form1()
    10         {
    11             InitializeComponent();
    12         }
    13 
    14         private void button1_Click(object sender, EventArgs e)
    15         {
    16             Thread thread1 = new Thread(UpdateLabel);//可以省略线程的委托类型ParameterizedThreadStart
    17             thread1.Start("label已更新");
    18         }
    19 
    20         private void UpdateLabel(object str)
    21         {
    22             if (label1.InvokeRequired)//不同线程为true,所以这里是true
    23             {               
    24                 BeginInvoke(new Action<string> (x => {label1.Text = x.ToString();}),str);    
    25             }
    26         }
    27     }
    28 }

    说明:Action是.NET预定义好的委托,可以简化委托的语法,如果不清楚它的用法,可以搜索“Action和Func的用法”。

    将上面的两种方法总结在同一段程序里面如下所示:(注意看代码中的注释)

     1 using System;
     2 using System.Threading;
     3 using System.Windows.Forms;
     4 
     5 namespace Windows跨线程调用控件
     6 {
     7     public partial class Form1 : Form
     8     {
     9         public Form1()
    10         {
    11             InitializeComponent();
    12 
    13             /************* 方法一 ************/
    14             //Control.CheckForIllegalCrossThreadCalls = false;
    15 
    16         }
    17 
    18         private void button1_Click(object sender, EventArgs e)
    19         {
    20             Thread thread1 = new Thread(new ParameterizedThreadStart(UpdateLabel));
    21             thread1.Start("label已更新");
    22         }
    23 
    24 
    25         //如果控件的 Handle(句柄) 是在与调用线程不同的线程上创建的(说明您必须通过 Invoke 方法对控件进行调用),则为 true;否则为 false。
    26         private void UpdateLabel(object str)
    27         {
    28             if (label1.InvokeRequired)//当是不同的线程在访问时为true,所以这里是true
    29             {
    30                 /************* 方法二 ************/
    31                 //Action<string> actionDelegate = (x) => { this.label1.Text = x.ToString(); };
    32 
    33                 ////如果已经创建控件的句柄,则除了 InvokeRequired 属性以外,控件上还有四个可以从【任何线程】上安全调用的方法,
    34                 ////它们是:Invoke、BeginInvoke、EndInvoke 和 CreateGraphics。 
    35                 ////在后台线程上创建控件的句柄之前调用 CreateGraphics 可能会导致非法的跨线程调用。 
    36                 ////对于所有其他方法调用,则应使用调用 (invoke) 方法之一封送对控件的线程的调用。
    37                 //this.label1.BeginInvoke(actionDelegate, str);
    38 
    39 
    40                 /************* 方法二(变式) ************/
    41                 //也可以直接用下面一句话来完成
    42                 //Control.BeginInvoke 方法有两个重载:BeginInvoke(Delegate)    ,BeginInvoke(Delegate, Object[]),下式用的是第二个重载
    43                 this.BeginInvoke(new Action<string>((x) => { label1.Text = x.ToString(); }), str);
    44 
    45                 //如果启动的多线程不需要带可变的参数,那更简单:
    46                 //label1.BeginInvoke(new Action(() => { label1.Text = "aaa"; }));         
    47             }
    48         }
    49     }
    50 }

    参考资料:

    http://www.cnblogs.com/TankXiao/p/3348292.html

    http://www.cnblogs.com/txw1958/archive/2012/08/21/csharp-crossthread-widget.html

    作者:xh6300

    --------------------------------------------

    本文系原创作品,转载请注明出处。如果您认为文章对您有帮助,可以点击下方的【好文要顶】或【关注我】;如果您想进一步表示感谢,可以通过网页右侧的【打赏】功能对我进行打赏。感谢您的支持,我会继续写出更多对大家有帮助的文章!文章有不理解的地方欢迎跟帖交流,博主经常在线!^_^

  • 相关阅读:
    python 日期封装
    uiautomator2 使用注意的地方
    django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.13 or newer is required; you have 0.9.3.
    解决 pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on '127.0.0.1' ([Errno 61] Conne
    mitmproxy 使用mitmdump 过滤请求
    -bash: findstr: command not found 问题解决
    Mac xlwings aem.aemsend.EventError: Command failed: The user has declined permission. (-1743)
    Jenkins 使用python进行调度,并下载apphost上的安装包
    微信公众号爬虫--历史文章
    demo_23 搜索历史数据持久化
  • 原文地址:https://www.cnblogs.com/xh6300/p/6067615.html
Copyright © 2020-2023  润新知