zoukankan      html  css  js  c++  java
  • C#中跨线程访问控件

    net 原则上禁止跨线程访问控件,因为这样可能造成错误的发生,推荐的解决方法是采用代理,用代理方法来间接操作不是同一线程创建的控件。 

    第二种方法是禁止编译器对跨线程访问作检查,可以实现访问,但是出不出错不敢保证Control.CheckForIllegalCrossThreadCalls = false;

    最近我在做一个项目,遇到了跨线程要去访问页面控件.但是总是提示出错,不能在其它线程中修改创建控件的线程的控件的值,后来采用了匿名代理,结果很轻松地解决了.解决过程如下:
    首先在窗体上,创建一个listbox,lable.

     1 using System;
     2 using System.Collections.Generic;
     3 using System.ComponentModel;
     4 using System.Data;
     5 using System.Drawing;
     6 using System.Text;
     7 using System.Windows.Forms;
     8 using System.Threading;
     9 
    10 namespace AccessControl
    11 {
    12     public partial class Form1 : Form
    13     {      
    14         public Form1()
    15         {
    16             InitializeComponent();
    17         }
    18 
    19         private void Form1_Load(object sender, EventArgs e)
    20         {  
    21             Thread newthread = new Thread(new ThreadStart(BackgroundProcess));
    22             newthread.Start();         
    23 
    24         }
    25 
    26         /// <summary> 
    27         /// 定义一个代理 
    28         /// </summary> 
    29         private delegate void CrossThreadOperationControl();
    30 
    31         private void BackgroundProcess()
    32         {
    33             // 将代理实例化为一个匿名代理 
    34             CrossThreadOperationControl CrossDelete = delegate()          
    35             {            
    36                 int i = 1;
    37                 while (i<5)
    38                 {
    39                    // 向列表框增加一个项目 
    40                     listBox1.Items.Add("Item " + i.ToString());                    
    41                     i++;
    42                 }
    43                 label1.Text = "我在新线程里访问这个lable!";
    44                 listBox1.Items.Add(label1.Text);
    45             }  ;
    46             listBox1.Invoke(CrossDelete);           
    47         }       
    48 
    49     }
    50 }

    希望这个小技巧能够对你的的学习和工作有所帮助.若有更好的办法来解决跨线程访问控件的问题,不防也拿出来大家分享一下.

    C#跨线程访问控件运行时错误,使用MethodInvoker即可解决:

    原代码:

            

     1 private void btnOK_Click(object sender, EventArgs e)
     2         {
     3             tslInfo.Text = "请稍候...";
     4 
     5 
     6             Thread td = new Thread(new ThreadStart(run));
     7             td.Start();
     8         }
     9 
    10 
    11         /// <summary>
    12         /// 线程方法
    13         /// </summary>
    14         private void run()
    15         {
    16             this.tslInfo.Text = "就绪";
    17         }

    修改后:


            

     1 private void btnOK_Click(object sender, EventArgs e)
     2         {
     3             tslInfo.Text = "请稍候...";
     4 
     5 
     6             Thread td = new Thread(new ThreadStart(threadRun));
     7             td.Start();
     8         }
     9 
    10 
    11         /// <summary>
    12         /// 原线程方法
    13         /// </summary>
    14         private void run()
    15         {
    16             this.tslInfo.Text = "就绪";
    17         }
    18 
    19         /// <summary>
    20         /// 线程方法
    21         /// </summary>
    22         private void threadRun()
    23         {
    24             MethodInvoker In = new MethodInvoker(run);
    25             this.BeginInvoke(In);
    26         }

    我们在做winform应用的时候,大部分情况下都会碰到使用多线程控制界面上控件信息的问题。然而我们并不能用传统方法来做这个问题,下面我将详细的介绍。

          首先来看传统方法:

         

     1  public partial class Form1 : Form
     2     {
     3         public Form1()
     4         {
     5             InitializeComponent();
     6         }
     7         private void Form1_Load(object sender, EventArgs e)
     8         {
     9             Thread thread = new Thread(ThreadFuntion);
    10             thread.IsBackground = true;
    11             thread.Start();
    12         }
    13         private void ThreadFuntion()
    14         {
    15             while (true)
    16             {
    17                 this.textBox1.Text = DateTime.Now.ToString();
    18                 Thread.Sleep(1000);
    19             }
    20         }
    21     }

           运行这段代码,我们会看到系统抛出一个异常:Cross-thread operation not valid:Control 'textBox1' accessed from a thread other than the thread it was created on . 这是因为.net 2.0以后加强了安全机制,不允许在winform中直接跨线程访问控件的属性。那么怎么解决这个问题呢,下面提供几种方案。

          第一种方案,我们在Form1_Load()方法中加一句代码:

          

    1 private void Form1_Load(object sender, EventArgs e)
    2       {
    3             Control.CheckForIllegalCrossThreadCalls = false;
    4             Thread thread = new Thread(ThreadFuntion);
    5             thread.IsBackground = true;
    6             thread.Start();
    7         }


          加入这句代码以后发现程序可以正常运行了。这句代码就是说在这个类中我们不检查跨线程的调用是否合法(如果没有加这句话运行也没有异常,那么说明系统以及默认的采用了不检查的方式)。然而,这种方法不可取。我们查看CheckForIllegalCrossThreadCalls 这个属性的定义,就会发现它是一个static的,也就是说无论我们在项目的什么地方修改了这个值,他就会在全局起作用。而且像这种跨线程访问是否存在异常,我们通常都会去检查。如果项目中其他人修改了这个属性,那么我们的方案就失败了,我们要采取另外的方案。

          下面来看第二种方案,就是使用delegate和invoke来从其他线程中控制控件信息。网上有很多人写了这种控制方式,然而我看了很多这种帖子,表明上看来是没有什么问题的,但是实际上并没有解决这个问题,首先来看网络上的那种不完善的方式:

     1 public partial class Form1 : Form
     2     {
     3         private delegate void FlushClient();//代理
     4         public Form1()
     5         {
     6             InitializeComponent();
     7         }
     8         private void Form1_Load(object sender, EventArgs e)
     9         {
    10             Thread thread = new Thread(CrossThreadFlush);
    11 
    12             thread.IsBackground=true;
    13             thread.Start();
    14         }
    15 
    16         private void CrossThreadFlush()
    17         {
    18             //将代理绑定到方法 
    19             FlushClient fc = new FlushClient(ThreadFuntion);
    20             this.BeginInvoke(fc);//调用代理
    21         }
    22         private void ThreadFuntion()
    23         {
    24             while (true)
    25             {
    26                 this.textBox1.Text = DateTime.Now.ToString();
    27                 Thread.Sleep(1000);
    28             }
    29         }
    30     }

           使用这种方式我们可以看到跨线程访问的异常没有了。但是新问题出现了,界面没有响应了。为什么会出现这个问题,我们只是让新开的线程无限循环刷新,理论上应该不会对主线程产生影响的。其实不然,这种方式其实相当于把这个新开的线程“注入”到了主控制线程中,它取得了主线程的控制。只要这个线程不返回,那么主线程将永远都无法响应。就算新开的线程中不使用无限循环,使可以返回了。这种方式的使用多线程也失去了它本来的意义。

           现在来让我们看看推荐的解决方案(建议用该方案):

     1 public partial class Form1 : Form
     2     {
     3         private delegate void FlushClient();//代理
     4         public Form1()
     5         {
     6             InitializeComponent();
     7         }
     8         private void Form1_Load(object sender, EventArgs e)
     9         {
    10             Thread thread = new Thread(CrossThreadFlush);
    11             thread.IsBackground = true;
    12             thread.Start();
    13         }
    14 
    15         private void CrossThreadFlush()
    16         {
    17             while (true)
    18             {
    19                 //将sleep和无限循环放在等待异步的外面
    20                 Thread.Sleep(1000);
    21                 ThreadFunction();
    22             }
    23         }
    24         private void ThreadFunction()
    25         {
    26             if (this.textBox1.InvokeRequired)//等待异步
    27             {
    28                 FlushClient fc = new FlushClient(ThreadFunction);
    29                 this.Invoke(fc);//通过代理调用刷新方法
    30             }
    31             else
    32             {
    33                 this.textBox1.Text = DateTime.Now.ToString();
    34             }
    35         }
    36     }

           运行上述代码,我们可以看到问题已经被解决了,通过等待异步,我们就不会总是持有主线程的控制,这样就可以在不发生跨线程调用异常的情况下完成多线程对winform多线程控件的控制了。

           对于深山老林提出的问题,我最近找到了更优的解决方案,利用了delegate的异步调用,大家可以看看:

     1 public partial class Form1 : Form
     2     {
     3         private delegate void FlushClient();//代理
     4         public Form1()
     5         {
     6             InitializeComponent();
     7         }
     8         private void Form1_Load(object sender, EventArgs e)
     9         {
    10             Thread thread = new Thread(CrossThreadFlush);
    11             thread.IsBackground = true;
    12             thread.Start();
    13         }
    14 
    15         private void CrossThreadFlush()
    16         {
    17 
    18              FlushClient=new FlushClient(ThreadFunction);
    19 
    20              FlushClient.BeginInvoke(null,null);
    21         }
    22         private void ThreadFunction()
    23         {
    24 
    25               while (true)
    26             {
    27                 this.textBox1.Text = DateTime.Now.ToString();
    28                 Thread.Sleep(1000);
    29             }
    30 
    31         }
    32     }

         这种方法也可以直接简化为(因为delegate的异步就是开了一个异步线程):

     1 public partial class Form1 : Form
     2     {
     3         private delegate void FlushClient();//代理
     4         public Form1()
     5         {
     6             InitializeComponent();
     7         }
     8         private void Form1_Load(object sender, EventArgs e)
     9         {
    10             Thread thread = new Thread(CrossThreadFlush);
    11              FlushClient=new FlushClient(ThreadFunction);
    12 
    13              FlushClient.BeginInvoke(null,null);
    14         }
    15 
    16          private void ThreadFunction()
    17         {
    18 
    19               while (true)
    20             {
    21                 this.textBox1.Text = DateTime.Now.ToString();
    22                 Thread.Sleep(1000);
    23             }
    24 
    25         }
    26     }
  • 相关阅读:
    电视盒子通过adb安装第三方应用
    采用网闸隔离的内外网如何进行数据的即时交互?
    数据结构-python-二叉树
    Python+uiautomator2写安卓手机脚本前期准备
    centos6.5离线rpm安装mysql
    centos7 nginx 启动脚本
    zabbix简单改造为免密登陆
    vue生命周期函数
    mysql 深入问题了解
    9. 回文数.py
  • 原文地址:https://www.cnblogs.com/hanc/p/3685995.html
Copyright © 2011-2022 走看看