理论

Windows的线程同步方式可分为2种,用户模式构造和内核模式构造。
内核模式构造:是由Windows系统本身使用,内核对象进行调度协助的。内核对象是系统地址空间中的一个内存块,由系统创建维护。
  内核对象为内核所拥有,而不为进程所拥有,所以不同进程可以访问同一个内核对象, 如进程,线程,作业,事件(不是那个事情),文件,信号量,互斥量等都是内核对象。
  而信号量,互斥体,事件是Windows专门用来帮助我们进行线程同步的内核对象。
  对于线程同步操作来说,内核对象只有2个状态, 触发(终止,true)、未触发(非终止,false)。 未触发不可调度,触发可调度。

内核模式需要将托管代码转化为用户代码,然后切换成内核代码,所有很浪费时间
用户模式构造:是由特殊CPU指令来协调线程,上节讲的volatile实现就是一种,Interlocked也是。  也可称为非阻塞线程同步。

WaitHandle类的刨析

aitHandle是C#编程中等待和通知机制的对象模型。

在windows编程中,通过API创建一个内核对象后会返回一个句柄,句柄则是每个进程句柄表的索引,而后可以拿到内核对象的指针、掩码、标示等。

而WaitHandle抽象基类类作用是包装了一个windows内核对象的句柄。我们来看下其中一个WaitOne的函数源码(略精简)。

System.Threading命名空间中提供了一个WaitHandle 的抽象基类

 public abstract partial class WaitHandle : MarshalByRefObject, IDisposable
{
。。。。。。。
EventWaitHandle 事件等待句柄不是 .NET 事件。 并不涉及任何委托或事件处理程序。 之所以使用“事件”一词是因为它们一直都被称为操作系统事件,并且向等待句柄发出信号可以向等待线程指明事件已发生。

IDisposable:继承该接口要用using,和try catch 即使释放。
MarshalByRefObject:分为Marshal、ByRefObject。在 .NET Remoting 中,不论是传值或传址,每一个对象都必须要继承 System.MarshalByRefObject 类别,才可以利用 .NET Remoting 来传输。该类可以穿越同步域、线程、appdomain、进程
Marshal:类似于序列化。
ByRefObject:传递对象引用(类似于文件的快捷方式)。
.NET Remoting:

.NET Remoting 是一项传统技术,保留该技术是为了向后兼容现有的应用程序,不建议对新的开发使用该技术。现在应该使用  Windows Communication Foundation (WCF) 来开发分布式应用程序。
.NET Remoting是微软随.NET推出的一种分布式应用解决方案,被誉为管理应用程序域之间的 RPC 的首选技,它允许不同应用程序域之间进行通信(这里的通信可以是在同一个进程中进行、一个系统的不同进程间进行、不同系统的进程间进行)。
更具体的说,Microsoft
.NET Remoting 提供了一种允许对象通过应用程序域与另一对象进行交互的框架。也就是说,使用.NET
Remoting,一个程序域可以访问另外一个程序域中的对象,就好像这个对象位于自身内部,只不过,对这个远程对象的调用,其代码是在远程应用程序域中进行的,例如在本地应用程序域中调用远程对象上一个会弹出对话框的方法,那么,这个对话框,则会在远程应用程序域中弹出。


即使是在同一个Domain里,但如果是在不同的Context中,也需要继承MarshalByRefObject才能访问

通过以上分析我们得出WatiHandle是一个可远程调用的对象。一般的对象只能在本地应用程序域之内被引用,而MarshalByRefObject对象可以跨越应用程序域边界被引用,甚至被远程引用。
远程调用时,将产生一个远程对象在本地的透明代理,通过此代理来进行远程调用。
特别注意:
MarshalByRefOjbect当被远端调用时候,通过生命期服务 LifeService 控制该结构的生命周期。默认情况下,如果不再有任何调用操作后大约15分钟将销毁该结构。
所以强制GC回收也不会释放该对象的。

WatiHandle是什么?有什么用

等待器,等待带事件发生。事件内部维护一个Boolean变量。事件为false,在事件上等待的线程就阻塞;事件为true,就能解除阻塞。

false就关闭砸门,true就是打开砸门。

和事件(AutoResetEvent类、ManualResetEvent类)配合使用。当这些事件调用set()方法时候,等待器就会收到信号。
具体过程:
创建一个等待事件(ManualResetEvent对象)
注册事件,waitthandle.waitany(事件);
当你在线程中执行完操作后,会告诉 WaitHandle"我已完成“ 事件. Set()。

派生类

WaitHandle:是一个抽象类,我们一般不直接用,而是用它的派生类:
  1. EventWaitHandle:一旦创建,命名事件就对所有进程中的全部线程可见。 因此,命名事件可用于同步进程和线程的活动。事件等待句柄不是 .NET 事件。 并不涉及任何委托或事件处理程序。 之所以使用“事件”一词是因为它们一直都被称为操作系统事件,并且向等待句柄发出信号可以向等待线程指明事件已发生。
  2. AutoResetEvent:只能表示本地等待句柄。 无法表示命名系统事件。
  3. ManualResetEvent:只能表示本地等待句柄。 无法表示命名系统事件。
  4. Mutex 互斥量:跨进程同步
  5. Semaphore 信号量:跨进程同步

WaitHandle类 成员

字段
    InvalidHandle:可以使用此值确定Handle属性是否包含有效的本机操作系统句柄。
    WaitTimeout:是个常数258。是waitAny的返回值之一,如果超时就返回258。
属性
    Handle 已过时
    SafeWaitHandle:一个代表本地操作系统句柄的SafeWaitHandle。不要手动关闭该句柄,因为当SafeWaitHandle试图关闭该句柄时,会导致ObjectDisposedException异常。
方法
Close:关闭当前WaitHandle持有的所有资源。WaitHandle 持有很多对象的句柄。必须关闭后才能释放
Dispose:释放当前WaitHandle对象。

WaitHandle.WaitAll

只能在多线程运行。在指定的时间内(-1表示无等待,或者具体的时间)一直等待。直到收到所有的子线程发出set信号或主线程等待超时。

 

WaitAll():
基于WaitmultipleObject,只支持MTAThreadAttribute 的线程,实现要比WaitSingleObject复杂的多,性能也不好,尽量少用。在传给WaitAny()和WaitAll()方法的数组中,包含的元素不能超过64个,否则方法会抛出一个System.NotSupportedException。
并且与旧版 COM 体系结构有奇怪的连接:这些方法要求调用方位于多线程单元中,该模型最不适合互操作性。
例如,WPF 或 Windows 应用程序的主线程无法在此模式下与剪贴板进行交互。我们稍后将讨论替代方案。SignalAndWait

WaitHandle.WaitAny

只能在多线程运行。在指定的时间内(-1表示无等待,或者具体的时间)一直等待。直到收到任意一个的子线程发出set信号或主线程等待超时。

WaitAny():
基于WaitmultipleObject,只支持MTAThreadAttribute 的线程,实现要比WaitSingleObject复杂的多,性能也不好,尽量少用。如果没有任何对象满足等待,并且WaitAny()设置的等待的时间间隔已过,则为返回WaitTimeout。在传给WaitAny()和WaitAll()方法的数组中,包含的元素不能超过64个,否则方法会抛出一个System.NotSupportedException。

WaitHandle.WaitOne

单线程或者多线程中,在指定的时间内(-1表示无等待,或者具体的时间)一直等待。直到收到set信号或等待超时。

WaitOne():事件内部维护一个Boolean变量。事件为false,在事件上等待的线程就阻塞;事件为true,就能解除阻塞。
基于WaitSingleObject   阻止当前线程,直到当前 WaitHandle 收到信号。
WaitOne(Int32 time) :
阻止当前线程,计时等待,在规定time之前收到信号,就返回true。否则返回false。-1表示无限等待。
WaitOne(Int32, Boolean):

在学习这个方法之前,你必须储备这些知识:NET Remoting 通信模型、MarshalByRefObject类、上下文绑定、同步域等概念。才会理解该方法的用法。

Int32:等待时间

Boolean:是否在执行waitone方法之前 退出同步上下文(因为此时waithandle捕获了同步域的上下文,只有当前线程退出后其他线程才能进入同步域),false 那么其他线程在当前线程执行waitone方法未超时期间无法进入同步域。true 其他线程可在当期线程等待期间可用进入。

除非从【非默认的托管上下文(指同步域的上下文)】中调用WaitOne方法,否则exitContext参数没有作用。默认的托管上下文就是AppDomain建立后就会建立一个默认的托管上下文。

当您的代码在非默认上下文中执行时,为exitContext指定true将导致线程在执行WaitOne方法之前退出非默认的托管上下文中(即转换到默认上下文中)。在对WaitOne方法的调用完成后,线程返回到原始的非默认上下文。
当上下文绑定类具有SynchronizationAttribute时,这可能很有用。在这种情况下,对类成员的所有调用都自动同步,同步域是类的整个代码体。如果成员的调用堆栈中的代码调用WaitOne方法,并为exitContext指定true,则线程退出同步域,从而允许在调用对象的任何成员时被阻塞的线程继续进行。当WaitOne方法返回时,进行调用的线程必须等待重新进入同步域。

您可以在任何 ContextBoundObject 子类上使用SynchronizationAttribute来同步所有实例方法和字段。同一上下文域中的所有对象共享同一锁。允许多个线程访问方法和字段,但一次只允许一个线程。

案例如下:

 案例一、

using System;
using System.Threading;
using System.Runtime.Remoting.Contexts; [Synchronization(true)]//允许线程重入
public class SyncingClass : ContextBoundObject
{
private EventWaitHandle waitHandle; public SyncingClass()
{
waitHandle =
new EventWaitHandle(false, EventResetMode.ManualReset);
} public void Signal()
{
Console.WriteLine("Thread[{0:d4}]: Signalling...", Thread.CurrentThread.GetHashCode());
waitHandle.Set();
} public void DoWait(bool leaveContext)
{
bool signalled; waitHandle.Reset();
Console.WriteLine("Thread[{0:d4}]: Waiting...", Thread.CurrentThread.GetHashCode());
signalled = waitHandle.WaitOne(3000, leaveContext);
if (signalled)
{
Console.WriteLine("Thread[{0:d4}]: Wait released!!!", Thread.CurrentThread.GetHashCode());
}
else
{
Console.WriteLine("Thread[{0:d4}]: Wait timeout!!!", Thread.CurrentThread.GetHashCode());
}
}
} public class TestSyncDomainWait
{
public static void Main()
{
SyncingClass syncClass = new SyncingClass(); Thread runWaiter; Console.WriteLine("\nWait and signal INSIDE synchronization domain:\n");
runWaiter = new Thread(RunWaitKeepContext);
runWaiter.Start(syncClass);
Thread.Sleep(1000);
Console.WriteLine("Thread[{0:d4}]: Signal...", Thread.CurrentThread.GetHashCode());
// This call to Signal will block until the timeout in DoWait expires.
syncClass.Signal();
runWaiter.Join(); Console.WriteLine("\nWait and signal OUTSIDE synchronization domain:\n");
runWaiter = new Thread(RunWaitLeaveContext);
runWaiter.Start(syncClass);
Thread.Sleep(1000);
Console.WriteLine("Thread[{0:d4}]: Signal...", Thread.CurrentThread.GetHashCode());
// This call to Signal is unblocked and will set the wait handle to
// release the waiting thread.
syncClass.Signal();
runWaiter.Join();
} public static void RunWaitKeepContext(object parm)
{
((SyncingClass)parm).DoWait(false);
} public static void RunWaitLeaveContext(object parm)
{
((SyncingClass)parm).DoWait(true);
}
} // The output for the example program will be similar to the following:
//
// Wait and signal INSIDE synchronization domain:
//因为线程4 未退出同步域,所以线程1一无法进入。只能等线程4超时后,线程1才能进入。
// Thread[0004]: Waiting...
// Thread[0001]: Signal...
// Thread[0004]: Wait timeout!!!
// Thread[0001]: Signalling...
//
// Wait and signal OUTSIDE synchronization domain:
//因为线程6,在执行waitone()之前已经退出同步域(回到默认域),所以线程1可用在线程6等待期间,进入同步域内执行方法Signal()
// Thread[0006]: Waiting...
// Thread[0001]: Signal...
// Thread[0001]: Signalling...
// Thread[0006]: Wait released!!!

案例二、

使用SynchronizationAttribute和ContextBoundObject一起组合创建一个简单的自动的同步。
该对象内部构成一个同步域。只允许一个线程进入。
将 SynchronizationAttribute应用于某个类后,该类的实例无法被多个线程同时访问。我们说,这样的类是线程安全的。
该方式实现的同步已经过时,只做了解。

using System;
using System.Threading;
using System.Runtime.Remoting.Contexts; [Synchronization]//不允许线程重入
public class AutoLock : ContextBoundObject
{
public void Demo()
{
Console.Write ("Start...");
Thread.Sleep (1000); // We can't be preempted here
Console.WriteLine ("end"); // thanks to automatic locking!
}
} public class Test
{
public static void Main()
{
AutoLock safeInstance = new AutoLock();
new Thread (safeInstance.Demo).Start(); // Call the Demo
new Thread (safeInstance.Demo).Start(); // method 3 times
safeInstance.Demo(); // concurrently.
}
}
输出:
Start... end
Start... end
Start... end
 

原因就是整个对象内部就是一个同步域(锁的临界区),就是在当前没处理完,其他线程是无法进行操作的。

CLR确保一次只有一个线程可以执行其中的代码。它通过创建一个同步对象来实现这一点——并在每个方法或属性的每次调用时锁定它。锁的作用域——在本例中同步对象——被称为同步上下文。safeinstancesafeinstance
那么,这是如何工作的呢?一个线索在属性的命名空间:。A可以被认为是一个“远程”对象,这意味着所有的方法调用都被拦截。为了使这种拦截成为可能,当我们实例化时,CLR实际上返回一个代理——一个具有与对象相同的方法和属性的对象,它充当中介。自动锁定就是通过这个中介发生的。总的来说,拦截在每个方法调用上增加了大约一微秒。。。点击查看内容来源

 

 关于退出上下文的说明
除非从非默认的托管上下文中调用WaitOne方法,否则exitContext参数没有作用。如果你的线程在调用一个从ContextBoundObject派生的类实例时,就会发生这种情况。即使您当前正在执行一个不是从ContextBoundObject派生的类上的方法,如String,如果ContextBoundObject在当前应用程序域中的堆栈上,您也可以处于非默认上下文中。
当您的代码在非默认上下文中执行时,为exitContext指定true将导致线程在执行WaitOne方法之前退出非默认的托管上下文中(即转换到默认上下文中)。在对WaitOne方法的调用完成后,线程返回到原始的非默认上下文。

当上下文绑定类具有SynchronizationAttribute时,这可能很有用。在这种情况下,对类成员的所有调用都自动同步,同步域是类的整个代码体。如果成员的调用堆栈中的代码调用WaitOne方法,并为exitContext指定true,则线程退出同步域,从而允许在调用对象的任何成员时被阻塞的线程继续进行。当WaitOne方法返回时,进行调用的线程必须等待重新进入同步域。

 WaitHandle.SignalAndWait

基于WaitmultipleObject,只支持MTAThreadAttribute 的线程。在指定的时间内(-1表示无限等待,或者具体的时间)一直等待。直到收到对应的子线程发出set信号或主线程等待超时。

SignalAndWait(WaitHandle ewh, WaitHandle clearCount)     
默认无期限(-1)的等待子线程的返回信号。给ewh 释放一个set()信号,然后当前进程处于阻塞状态,切换到SignalAndWait所在的线程中执行,当子线程运行到 clearCount.Set();又切换到当前进程执行。 在具有 STAThreadAttribute 的线程中不支持 SignalAndWait ()方法。
SignalAndWait(WaitHandle, WaitHandle, Int32 time, Boolean)
time表示在子线程中等待N秒钟,如果未等到子线程的信号,就切回到SignalAndWait所在的线程,true表示退出子线程上下文。 这样其他线程就可用进入子线程的代码区。 进行执行在具有 STAThreadAttribute 的线程中不支持 SignalAndWait()方法。
SignalAndWait(WaitHandle, WaitHandle, TimeSpan, Boolean)
设定一个超时时间。在具有 STAThreadAttribute 的线程中不支持 SignalAndWait ()方法。

 案例 运行轨迹如吓
using System;
using System.Threading; public class Example
{
// The EventWaitHandle used to demonstrate the difference
// between AutoReset and ManualReset synchronization events.
//
private static EventWaitHandle ewh; // A counter to make sure all threads are started and
// blocked before any are released. A Long is used to show
// the use of the 64-bit Interlocked methods.
//
private static long threadCount = 0; // An AutoReset event that allows the main thread to block
// until an exiting thread has decremented the count.
//
private static EventWaitHandle clearCount =
new EventWaitHandle(false, EventResetMode.AutoReset); [MTAThread]
public static void Main()
{
// Create an AutoReset EventWaitHandle.
//
ewh = new EventWaitHandle(false, EventResetMode.AutoReset); for (int i = 0; i <= 4; i++)
{
Thread t = new Thread(
new ParameterizedThreadStart(ThreadProc)
);
t.Start(i);
} //
while (Interlocked.Read(ref threadCount) < 5)
{
Thread.Sleep(500);
} // 当线程都处于阻塞后运行到这一步
while (Interlocked.Read(ref threadCount) > 0)
{ //给ewh 释放一个set()信号,然后当前进程处于阻塞状态,切换到子线程中执行,当子线程运行到 clearCount.Set();又切换到当前进程执行。
// 如果都完成了就返回true。
WaitHandle.SignalAndWait(ewh, clearCount);
//2000表示在子线程中等待2s中,如果未等到子线程信号,就切回到主线程,true表示退出子线程上下文。 这样其他线程就可用进入代码区 进行执行
// WaitHandle.SignalAndWait(ewh, clearCount,2000,true);
}
Console.WriteLine(); // Create a ManualReset EventWaitHandle.
//
ewh = new EventWaitHandle(false, EventResetMode.ManualReset); // Create and start five more numbered threads.
//
for (int i = 0; i <= 4; i++)
{
Thread t = new Thread(
new ParameterizedThreadStart(ThreadProc)
);
t.Start(i);
} // Wait until all the threads have started and blocked.
//
while (Interlocked.Read(ref threadCount) < 5)
{
Thread.Sleep(500);
} Console.WriteLine("Press ENTER to release the waiting threads.");
Console.ReadLine();
ewh.Set();
} public static void ThreadProc(object data)
{
int index = (int)data; Console.WriteLine("Thread {0} blocks.", data);
// Increment the count of blocked threads.
Interlocked.Increment(ref threadCount); //线程在这边阻塞等待 信号。
ewh.WaitOne(); Console.WriteLine("Thread {0} exits.", data);
// Decrement the count of blocked threads.
Interlocked.Decrement(ref threadCount);
//这个执行完成后,会切回到主线程
// clearCount.Set();
}
}

【C# 线程】WaitHandle类的更多相关文章

  1. 线程 ManualResetEvent 类

    Reset(): 当一个线程开始一个活动(此活动必须完成后,其他线程才能开始)时, 它调用 Reset 以将 ManualResetEvent 置于非终止状态.此线程可被视为控制 ManualRese ...

  2. FreeOnTerminate 的线程在线程管理类的Destroy释放时手工释放的问题

    这个问题折腾了我整整一天. 有一个线程管理类,集中管理所有新建的线程, 线程统一在创建时标识 FreeOnTerminate 为 True. 因为有的线程是不限次循环的,所以在管理类最后 Destro ...

  3. Qt 学习之路 :Qt 线程相关类

    希望上一章有关事件循环的内容还没有把你绕晕.本章将重新回到有关线程的相关内容上面来.在前面的章节我们了解了有关QThread类的简单使用.不过,Qt 提供的有关线程的类可不那么简单,否则的话我们也没必 ...

  4. C#中假设正确使用线程Task类和Thread类

    C#中使用线程Task类和Thread类小结 刚接触C#3个月左右.原先一直使用C++开发.由于公司的须要,所地採用C#开发.主要是控制设备的实时性操作,此为背景. 对于C#中的Task和Thread ...

  5. 工作线程基类TaskSvc

    工作线程基类TaskSvc 前端时间用ACE写代码,发ACE_Task确实好用.不但能提供数量一定的线程,还能够让这些继承的线程函数自由访问子类的private和protected变量.此外,ACE_ ...

  6. Java多线程之线程其他类

    Java多线程之线程其他类 实际编码中除了前面讲到的常用的类之外,还有几个其他类也有可能用得到,这里来统一整理一下: 1,Callable接口和Future接口 JDK1.5以后提供了上面这2个接口, ...

  7. 『TensorFlow』线程控制器类&变量作用域

    线程控制器类 线程控制器原理: 监视tensorflow所有后台线程,有异常出现(主要是越界,资源循环完了)时,其should_stop方法就会返回True,而它的request_stop方法则用于要 ...

  8. linux通过c++实现线程池类

    目录 线程池的实现 线程池已基于C++11重写 : 基于C++11实现线程池的工作原理 前言 线程池的概念 使用原因及适用场合 线程池的实现原理 程序测试 线程池的实现 线程池已基于C++11重写 : ...

  9. java并发编程:线程安全管理类--原子包--java.util.concurrent.atomic

    java.util.concurrent.atomic 的描述 AtomicBoolean 可以用原子方式更新的 boolean 值. AtomicInteger 可以用原子方式更新的 int 值. ...

随机推荐

  1. Java 面试题史上最强整理

    https://mp.weixin.qq.com/s/kJpRgfI3zT77XqMeRfmmQQ

  2. 搭建服务器之DNS

    DNS服务器,实用软件为bind,服务守护进程为named,一下记录一下自己的搭建过程: 1.yum install bind*  其中包括bind本身软件,测试dns的一些工具dig,nslooku ...

  3. vue学习10-计算属性

    计算属性 1 <!DOCTYPE html> 2 <html lang='en'> 3 <head> 4 <meta charset='UTF-8'> ...

  4. migrate 和makemigrations 命令

    在你改动了app下 models.py的内容之后执行下面的命令: Python manger.py makemigrations 相当于 在该app下建立 migrations目录,并记录下你所有的关 ...

  5. Android Studio IDE 插件开发

    作者:字节跳动终端技术--周宸韬 概述 这篇文章旨在向读者介绍IntelliJ IDE插件的开发流程以及常用的一些通用功能,任何基于IntelliJ开发的IDE都可以通过该方式制作插件,例如Andro ...

  6. List<Integer>里有可能存String类型元素吗?

    这其实是我遇到的一个线上bug,在这里分享给大家. 如果是用反射,那就很简单了,毕竟泛型只是在编译期进行约束,对运行期是无能为力的. 想想看,如果不使用反射,有没有办法做到呢? 问题起因 在我们公司的 ...

  7. python 小兵(12)模块1

    序列化 我们今天学习下序列化,什么是序列化呢? 将原本的字典.列表等内容转换成一个字符串的过程就叫做序列化. 为什么要有序列化模块: 比如,我们在python代码中计算的一个数据需要给另外一段程序使用 ...

  8. java实现多线程生产者消费者模式

    1.概念 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题.生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消 ...

  9. JS generator(生成器)

    笔记整理自:廖雪峰老师的JS教程 目录 简介 与函数的不同之处 函数写法 generator写法 generator调用 generator对象的`next()`方法调用 `for ... of`循环 ...

  10. js中全局和局部变量的区别

    2 3 4 5 6 7 8 9 10 <script type="text/javascript"> var a = 1; function hehe() {      ...