C#多线程操作界面控件的解决方案

  • Post author:
  • Post category:其他


C#中利用委托实现多线程跨线程操作

– 张小鱼 2010-10-22 08:38

在使用VS2005的时候,如果你从非创建这个控件的线程中访问这个控件或者操作这个控件的话就会抛出这个异常。这是微软为了保证线程安全以及提高代码的效率所做的改进,但是也给大家带来很多不便。

其实解决这个问题有两种方法:


一,是通过设置



System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;


在你的程序初始化的时候设置了这个属性,而且在你的控件中使用的都是微软Framework类库中的控件的话,系统就不会再抛出你上面所说的这个错误了。


二,就是委托了,个人建议用这种方法


首先在WinForm窗体中拖入

ListBox

控件,然后参照以下代码:

Thread t1;//声明一个全局线程 private void Form1_Load(object sender, EventArgs e) { t1 = new Thread(new ThreadStart(BackgroundProcess)); t1.Start(); //开始 } delegate void aa(); private void BackgroundProcess() { // 将委托实例化 aa a= delegate() { for (int i = 0; i < 50; i++) { listBox1.Items.Add(“Iterations: ” + i.ToString()); Thread.Sleep(300); listBox1.Refresh(); } }; listBox1.Invoke(a); }

来源:http://www.huomo.cn/developer/article-1aa8.html


C#多线程操作界面控件的解决方案



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

首先来看传统方法:

public partial class Form1 : Form­ {­ public Form1()­ {­ InitializeComponent();­ }­ private void Form1_Load(object sender, EventArgs e)­ {­ Thread thread = new Thread(ThreadFuntion);­ thread.IsBackground = true;­ thread.Start();­ }­ private void ThreadFuntion()­ {­ while (true)­ {­ this.textBox1.Text = DateTime.Now.ToString();­ Thread.Sleep(1000);­ }­ }­ }­

运行这段 代码,我们会看到系统抛出一个异常: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()方法中加一句代码:­

private void Form1_Load(object sender, EventArgs e)­ {­ Control.CheckForIllegalCrossThreadCalls = false;­ Thread thread = new Thread(ThreadFuntion);­ thread.IsBackground = true;­ thread.Start();­ }­

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

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

public partial class Form1 : Form­ {­ private delegate void FlushClient();//代理­ public Form1()­ {­ InitializeComponent();­ }­ private void Form1_Load(object sender, EventArgs e)­ {­ Thread thread = new Thread(CrossThreadFlush);­ thread.IsBackground=true;­ thread.Start();­ }­ private void CrossThreadFlush()­ {­ //将代理绑定到方法 ­ FlushClient fc = new FlushClient(ThreadFuntion);­ this.BeginInvoke(fc);//调用代理­ }­ private void ThreadFuntion()­ {­ while (true)­ {­ this.textBox1.Text = DateTime.Now.ToString();­ Thread.Sleep(1000);­ }­ }­ }­

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

现在来让我们看看推荐的解决方案:­

public partial class Form1 : Form­ {­ private delegate void FlushClient();//代理­ public Form1()­ {­ InitializeComponent();­ }­ private void Form1_Load(object sender, EventArgs e)­ {­ Thread thread = new Thread(CrossThreadFlush);­ thread.IsBackground = true;­ thread.Start();­ }­ private void CrossThreadFlush()­ {­ while (true)­ {­ //将sleep和无限循环放在等待异步的外面­ Thread.Sleep(1000);­ ThreadFunction();­ }­ }­ private void ThreadFunction()­ {­ if (this.textBox1.InvokeRequired)//等待异步­ {­ FlushClient fc = new FlushClient(ThreadFunction);­ this.Invoke(fc);//通过代理调用刷新方法­ }­ else­ {­ this.textBox1.Text = DateTime.Now.ToString();­ }­ }­ }­

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

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

public partial class Form1 : Form­ {­ private delegate void FlushClient();//代理­ public Form1()­ {­ InitializeComponent();­ }­ private void Form1_Load(object sender, EventArgs e)­ {­ Thread thread = new Thread(CrossThreadFlush);­ thread.IsBackground = true;­ thread.Start();­ }­ private void CrossThreadFlush()­ {­ FlushClient=new FlushClient(ThreadFunction);­ FlushClient.BeginInvoke(null,null);­ }­ private void ThreadFunction()­ {­ while (true)­ {­ this.textBox1.Text = DateTime.Now.ToString();­ Thread.Sleep(1000);­ }­ ­ }­ }­

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

public partial class Form1 : Form­ {­ private delegate void FlushClient();//代理­ public Form1()­ {­ InitializeComponent();­ }­ private void Form1_Load(object sender, EventArgs e)­ {­ Thread thread = new Thread(CrossThreadFlush);­ FlushClient=new FlushClient(ThreadFunction); ­ FlushClient.BeginInvoke(null,null);­ }­ private void ThreadFunction()­ {­ while (true)­ {­ this.textBox1.Text = DateTime.Now.ToString();­ Thread.Sleep(1000);­ }­ ­ }­ }

public  partial  class Form1 : Form

{

private  delegate  void ThreadWork( int i);

Thread thread;

public Form1()

{

InitializeComponent();

//  CheckForIllegalCrossThreadCalls = false;

}

private  void button1_Click( object sender, EventArgs e)

{

this.richTextBox1.Text =  ” 程序开始:\r\n “;

UpdateText();

}

public  void UpdateText()

{

thread =  new Thread( new ThreadStart(CrossThreadFlush));

thread.IsBackground =  true;

thread.Start();

}

private  void CrossThreadFlush()

{

while ( true)

{  // 将sleep和无限循环放在等待异步的外面

for ( int i =  1; i <  100; i++)

{

ThreadFunction(i);

Thread.Sleep( 500);

}

}

}

private  void ThreadFunction( int i)

{

if ( this.richTextBox1.InvokeRequired) // 等待异步

{

ThreadWork fc =  new ThreadWork(ThreadFunction);

//  this.Invoke(fc); // 通过代理调用刷新方法

this.Invoke(fc,  new  object[ 1] { i });

}

else

{

this.richTextBox1.Text =  string.Format( ” 第{0}个\t{1:mm-ss}\r\n “, i, DateTime.Now) +  this.richTextBox1.Text;

this.richTextBox1.Refresh();

}

}

}

}

文章摘自:

C #中的几个线程同步对象方法

– 张小鱼 2010-10-22 08:40

在编写多线程程序时无可避免会遇到线程的同步问题。什么是线程的同步呢?

举 个例子:如果在一个公司里面有一个变量记录某人T的工资count=100,有两个主管A和B(即工作线程)在早一些时候拿了这个变量的值回去 ,过了一段时间A主管将T的工资加了5块,并存回count变量,而B主管将T的工资减去3块,并存回count变量。好了,本来T君可以得到102块的 工资的,现在就变成98块了。这就是线程同步要解决的问题。

在C#里面用于实现线程同步的常用类有如下几类 1、Mutex类(互斥器),Monitor类,lock方法

2、ManualResetEvent类,AutoResetEvent类(这两个都是由EventWaitHandle类派生出来的)

3、ReaderWriterLock类

同 一类的作用都差不多:其中 代码执行为止。就好比一堆人同时上一个公共厕所一样,使用这个方法就可以解决文章一开始时提出的问题:主管A要处理T君的工资之前,先lock一下T君, 然后取出目前的count值,处理完之后再解除T君的锁定。如果主管B在主管A处理工资时也想取出count值,那么它只能是一直地等待A处理完之后才能 继续。使用这个方法的一个缺点就是会降低程序的效率。本来是一个多个线程的操作,一旦遇到lock的语句时,那么这些线程只要排队处理,形同一个单线程操 作。

下面举个例子说明一下这三个方法的使用:

假定有一个Tools类,里面一个int变量,还有Add和Delete方法,其中Add方法会使int变量的值增加,Delete方法使int变量值减少:

public class Tools private int count = 100;

public void Add(int n) count+=n;

}

public void Delete(int n) count-=n;

}

在多个线程同时访问这段代码时,因为一个语句会被编译器编译成多个指令,所以会可能出现这种情况:但某个线程调用Add方法时,这时的count值为 100,而正当要加上n的时候,另外一个线程调用了Delete,它要减去m,结果count加上了n,然后又在原先count=100的值的情况

下减掉了m,最后的结果是count被减去了m,而没有加上n。很明显Add方法和Delete方法是不能同时被调用的,所以必须进行线程同步处理。简单的方法是用lock语句:

public class Tools private object abcde = new object();

private int count = 100;

public void Add(int n) lock(abcde) count+=n;

}

public void Delete(int n) lock(abcde) count-=n; }

其中abcde是一个private级的内部变量,它不表示任何的意义,只是作为一种“令牌”的角色。

当执行Add方法中的lock(abcde)方法时,这个令牌就在Add方法的手中了,如果这时有第二个线程也想拿这个令牌,没门,惟有等待。一旦第一

个lock语句的花括号范围结束之后,这时令牌就被释放了,同时会迅速落到第二个线程的手中,并且排除其他后来的人。

使用Monitor类的方法大致一样:

public class Tools private object abcde = new object();

private int count = 100;

public void Add(int n) Monitor.Enter(abcde);

count+=n;

Monitor.Exit(abcde);

}

public void Delete(int n) Monitor.Enter(abcde);

count-=n;

Monitor.Exit(abcde);

}

Monitor的常用方法:Enter和Exit都是静态方法,作用跟lock语句的两个花括号一样。

而使用 Mutex 就不需声明一个“令牌”对象了,但要实例化之后才可以使用:

public class Tools

{

private Mutex mut = new Mutex();

private int count = 100;

public void Add(int n) mut.WaitOne();

count+=n;

mut.ReleaseMutex();

}

public void Delete(int n) mut.WaitOne();

count-=n;

mut.ReleaseMutex();

}

其中的WaitOne为等待方法,一直等到Mutex 被释放为止。初始的情况下,Mutex 对象是处于释放状态的,而一旦执行了WaitOne方法之后,它 就被捕获了,一直到被调用了ReleaseMutex方法之后才被释放。

使用这三种方法都有一个要注意的问题,就是在独占代码段里面如果引起了异常,可能会使“令牌”对象不被释放,这样程序就会一直地死等下去了。

所以要在独占代码段里面处理好异常。例如下面这样的代码就是错误的:

public void Add(int n) try mut.WaitOne();

count+=n; mut.ReleaseMutex(); catch Console.Writeline(“error.”); }

上面的代码一旦在try和catch里面发生了异常,那么Mutex就不能被释放,后面的程序就会卡死在WaitOne()一行,而应该改成这样:

public void Add(int n) mut.WaitOne();

try count+=n; }

catch Console.Writeline(“error.”); mut.ReleaseMutex();

}

现在谈一下第二种:

ManualResetEvent类,AutoResetEvent类

上面这两个类都是由EventWaitHandle类派生出来的,所以功能和调用方法都很相似。 举个例子,你想送花给一个MM,托了一个送花的小伙子送了过去,而你希望当MM收到花之后就立即打个电话过去告诉她。

但问题是你不知道花什么时候才送到MM的手里,打早了打迟了都不好,这时你可以使用ManualResetEvent对象帮忙。

当委托小伙子送花过去的时候,使用ManualResetEvent的WaitOne方法进行等待。当小伙子把花送到MM的手中时,再调用一下ManualResetEvent的Set方法,你就可以准时地打电话过去了。

另外ManualResetEvent还有一