精益求精C#程序的指出-在线程同步中使用信号量

所谓线程同步,就是五个线程之间在某个对象上推行等待(也可领悟为锁定该目标),直到该对象被扫除锁定。C#中目的的品种分为引用类型和值类型。CLR在这两体系型上的等候是不相同的。大家得以简简单单的领会为在CLR中,值类型是不可能被锁定的,也即:不可以在一个值类型对象上实施等待。而在引用类型上的等候机制,则分为两类:锁定和信号同步。

锁定,使用重要字lock和类型Monitor。两者没有精神区别,前者其实是后者的语法糖。这是最常用的同台技术;

本提议我们啄磨的是信号同步。信号同步机制中提到的门类都持续自抽象类WaitHandle,这多少个品种有伊芙(Eve)ntWaitHandle(类型化为AutoReset伊夫(Eve)nt、ManualReset伊夫nt)和Semaphore以及Mutex。见类图6-3:

图片 1

图 一道功用类类图

伊夫ntWaitHandle(子类为AutoReset伊芙nt、ManualReset伊夫(Eve)nt)和Semaphore以及Mutex都延续自WaitHandle,所以它们底层的规律是一样的,维护的都是一个系统基本句柄。不过我们仍需简单的区别下这三类类型。

伊夫ntWaitHandle,维护一个由基本暴发的布尔类型对象(我们称为“阻滞状态”),假使其值为false,那么在它上边等待的线程就短路。可以调用类型的Set方法将其值设置为true,解除阻塞。伊夫ntWaitHandle类型的几个子类AutoReset伊芙(Eve)nt和ManualReset伊夫nt,它们的区分并不大,本提议接下来会指向它们讲演怎样科学使用信号量。

Semaphore,维护一个由基本暴发的整型变量,假使其值为0,则在它上边等待的线程就不通,其值大于0,就撤销阻塞,同时,每解除阻塞一个线程,其值就减1。

伊夫ntWaitHandle和Semaphore提供的都是单运用程序域内的线程同步效率,Mutex则不同,它为我们提供了跨应用程序域阻塞和排除阻塞线程的能力。


1:使用信号机制提供线程同步的一个简短的例证

使用信号机制提供线程同步的一个粗略的例子如下:

AutoResetEvent autoResetEvent =new AutoResetEvent(false);

privatevoid buttonStartAThread_Click(object sender, EventArgs e)
{
Thread tWork =new Thread(() =>
{
label1.Text =”线程启动…”+ Environment.NewLine;
label1.Text +=”起首拍卖局部事实上的干活”+ Environment.NewLine;
//省略工作代码
label1.Text +=”我起来等候此外线程给本人信号,才愿意继续下去”+
Environment.NewLine;
autoResetEvent.WaitOne();
label1.Text +=”我继续做一些行事,然后截至了!”;
//省略工作代码
});
tWork.IsBackground =true;
tWork.Start();
}

privatevoid buttonSet_Click(object sender, EventArgs e)
{
//给在autoReset伊夫nt上等候的线程一个信号
autoResetEvent.Set();
}

这是一个简便的Winform窗体程序,其中一个按钮负责开启一个新的线程,还有一个按钮负责给刚打开的非凡线程发送信号。现在详细说明这之中暴发的作业。

AutoResetEvent autoResetEvent =new AutoResetEvent(false);

这段代码创设了一个齐声类型对象autoReset伊夫(Eve)nt,它设置自己的默认阻滞状态是false。这代表任何在它下面举行等待的线程将会被堵住。所谓举行等待,就是在线程中采用:

autoResetEvent.WaitOne();

这表明tWork起初在autoReset伊夫nt上等待其他其他地点给它的信号。信号来了,则tWork伊始连续做事,否则就径直等着(即阻滞)。接下来我们看出在主线程中(本例中即UI线程,它相对线程tWork来说,就是一个“其余的线程”):

autoResetEvent.Set();

主线程通过地点那句代码负责向在autoReset伊夫(Eve)nt上等候的线程tWork上下文发送信号,即将tWork的阻止状态设置为true。tWork接收到这么些信号,起始连续工作。

这一个事例相当简单,但是曾经全体表达了信号机制的行事规律。

2:AutoResetEvent和ManualResetEvent的区别

AutoReset伊芙nt和ManualReset伊芙(Eve)nt有如此的分别:前者在殡葬信号完毕后(即调用Set方法),自动将团结的阻碍状态设置为false,而后人需要展开手动设定。可以经过一个事例来证实这种区别:

AutoResetEvent autoResetEvent =new AutoResetEvent(false);

privatevoid buttonStartAThread_Click(object sender, EventArgs e)
{
StartThread1();
StartThread2();
}

privatevoid StartThread1()
{
Thread tWork1 =new Thread(() =>
{
label1.Text =”线程1启动…”+ Environment.NewLine;
label1.Text +=”起首拍卖部分事实上的工作”+ Environment.NewLine;
//省略工作代码
label1.Text +=”我初始等候另外线程给本人信号,才甘心继续下去”+
Environment.NewLine;
autoResetEvent.WaitOne();
label1.Text +=”我连续做一些做事,然后截至了!”;
//省略工作代码
});
tWork1.IsBackground =true;
tWork1.Start();
}

privatevoid StartThread2()
{
Thread tWork2 =new Thread(() =>
{
label2.Text =”线程2启动…”+ Environment.NewLine;
label2.Text +=”先导拍卖局部其实的干活”+ Environment.NewLine;
//省略工作代码
label2.Text +=”我最先等待其它线程给我信号,才甘心继续下去”+
Environment.NewLine;
autoResetEvent.WaitOne();
label2.Text +=”我连续做一些行事,然后停止了!”;
//省略工作代码
});
tWork2.IsBackground =true;
tWork2.Start();
}

privatevoid buttonSet_Click(object sender, EventArgs e)
{
//给在autoReset伊芙(Eve)nt上等候的线程一个信号
autoResetEvent.Set();
}

以此例子的本心是要让新起的两个办事线程tWork1和tWork2都阻止起来,直到收到主线程的信号再持续做事。结果程序运行的结果是,只有一个干活线程继续做事,另外一个工作线程则连续维持阻滞状态。我想原因我们都已经想到了。由于AutoReset伊夫(Eve)nt在发送信号完毕就在基本中活动将团结的状况设置回false了,所以此外一个干活线程相当于根本没有接收主线程的信号。

要修正这么些题材,可以运用ManualReset伊夫(Eve)nt。我们可以换成ManualReset伊芙(Eve)nt试一下。

3:应用实例

最终,再举一个内需用到线程同步的其实例子:模拟网络通信。客户端在运行过程中,服务器每隔一段的光阴会给客户端发送心跳多少。实际工作中服务器和客户端会是网络中两台不同的顶峰,在这么些例子中我们举行了简化。工作线程tClient模拟客户端,主线程(UI线程)模拟服务器端。客户端每3秒检测是否接收服务器的心跳多少,假如没有心跳数据,则显示网络连接断开。代码如下:

AutoResetEvent autoResetEvent =new AutoResetEvent(false);

privatevoid buttonStartAThread_Click(object sender, EventArgs e)
{
Thread tClient =new Thread(() =>
{
while (true)
{
//等3秒,3秒没有信号,展现断开
//有信号,则彰显更新
bool re = autoResetEvent.WaitOne(3000);
if (re)
{
label1.Text =string.Format(“时间:{0},{1}”, Date提姆(Tim)e.Now.ToString(),
“保持连续情状”);
}
else
{
label1.Text =string.Format(“时间:{0},{1}”, Date提姆(Tim)e.Now.ToString(),
“断开,需要重启”);
}
}
});
tClient.IsBackground =true;
tClient.Start();
}

privatevoid buttonSet_Click(object sender, EventArgs e)
{
//模拟发送心跳多少
autoResetEvent.Set();
}

备考:由本问题牵动一个Winform跨线程控件赋值和操作的问题。由于在本示例中不影响方面代码的周转,所以并未涉嫌,不过还原中有人提议来,所以提前简述一下Winform的线程模型:

在Winform框架中,有一个ISynchronizeInvoke接口,所有的UI元素(表现为Control)都继承了该接口。其中,接口中的InvokdRequired属性表示了如今线程是否是创设它的线程。接口中的Invoke和BeginInvoke方法负责将信息发送到音讯队列中,这样,UI线程就可知正确处理它。

实际到代码中,对于夸线程控件赋值,可以行使下边的措施:

 

this.label1.BeginInvoke(new
Action(()=>
{
this.label1.Text =”跨线程中赋值”;
}));

转于:http://www.csharpwin.com/csharpspace/12757r3149.shtml

相关文章