c#多线程通信之委托事件

在研究c# 线程之间通信时,发现传统的方法大概有三种:

  1. 全局变量,由于同一进程下的多个进程之间共享数据空间,所以使用全局变量是最简单的方法,但要记住使用volatile进行限制。

  2. 线程之间发送消息(这个随后文章中会讨论到)。

  3. CEvent为MFC中的一个对象,可以通过对CEvent的触发状态进行改变,从而实现线程间的通信和同步,这个主要是实现线程直接同步的一种方法。

本文介绍的一种方法是这三种之外的一种方法,本文中实例是通过创建一个线程类,通过委托事件把值传送到Form所在的类中,同时更新Form类中的一个控件(Label)中的值。

实现功能比较简单,目的是实现此功能,先把代码贴上:

MyThread.cs

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;
using System.Threading;
  
namespace ThreadsComm
{
 public delegate void ReadParamEventHandler(string sParam);
 class MyThread
 {
  public Thread thread1;
  private static ReadParamEventHandler OnReadParamEvent;
  public MyThread()
  {
   thread1 = new Thread(new ThreadStart(MyRead));
   thread1.IsBackground = true;
   thread1.Start();
  }
  public event ReadParamEventHandler ReadParam
  {
   add { OnReadParamEvent += new ReadParamEventHandler(value);}
   remove{ OnReadParamEvent -= new ReadParamEventHandler(value);}
  }
  protected void MyRead()
  {
   int i = 0;
   while (true)
   {
    Thread.Sleep(1000);
    i = i + 1;
    OnReadParamEvent(i.ToString());//触发事件
   }
  }
 }
}

其中的

?
1
2
3
4
5
public event ReadParamEventHandler ReadParam
  {
   add { OnReadParamEvent += new ReadParamEventHandler(value);}
   remove{ OnReadParamEvent -= new ReadParamEventHandler(value);}
  }

这个需要说明一下:

add 上下文关键字用于定义一个自定义事件访问器,当客户端代码订阅您的事件时将调用该访问器。 如果提供自定义 add 访问器,还必须提供 remove 访问器。

remove 上下文关键字用于定义一个自定义事件访问器,当客户端代码取消订阅事件时将调用该访问器。 如果提供自定义 remove 访问器,还必须提供 add 访问器。
Form.cs

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using System;
using System.Windows.Forms;
  
namespace ThreadsComm
{
 public partial class Form1 : Form
 {
  private static string param = "";
  public Form1()
  {
   InitializeComponent();
   MyThread thread1 = new MyThread();
   thread1.ReadParam += this.OnRead;
  }
  
  private void OnRead(string sParam)
  {
   param = sParam;
   Object[] list = { this,System.EventArgs.Empty};
   this.lblShow.BeginInvoke(new EventHandler(LabelShow), list);
  }
  protected void LabelShow(Object o, EventArgs e)
  {
   this.lblShow.Text = param;
  }
 }
}

其中的

?
1
2
MyThread thread1 = new MyThread();
thread1.ReadParam += this.OnRead;

是订阅线程类中的事件。

?
1
this.lblShow.BeginInvoke(new EventHandler(LabelShow), list);

Invoke或者 BeginInvoke方法都需要一个委托对象作为参数。委托类似于回调函数的地址,因此调用者通过这两个方法就可以把需要调用的函数地址封送给界面线程。这些方法里面如果包含了更改控件状态的代码,那么由于最终执行这个方法的是界面线程,从而避免了竞争条件,避免了不可预料的问题。如果其它线程直接操作界面线程所属的控件,那么将会产生竞争条件,造成不可预料的结果。

使用 Invoke完成一个委托方法的封送,就类似于使用 SendMessage方法来给界面线程发送消息,是一个同步方法。也就是说在 Invoke封送的方法被执行完毕前, Invoke方法不会返回,从而调用者线程将被阻塞。

使用 BeginInvoke方法封送一个委托方法,类似于使用 PostMessage进行通信,这是一个异步方法。也就是该方法封送完毕后马上返回,不会等待委托方法的执行结束,调用者线程将不会被阻塞。但是调用者也可以使用 EndInvoke方法或者其它类似 WaitHandle机制等待异步操作的完成。

但是在内部实现上, Invoke和 BeginInvoke都是用了 PostMessage方法,从而避免了 SendMessage带来的问题。而 Invoke方法的同步阻塞是靠 WaitHandle机制来完成的。

想实验的读者可以建一个winform工程,采用上边的代码试验一下。

(0)

相关推荐