一、前言
官方文档
WaitHandle位于System.Threading命名空间下,是用来封装等待对共享资源进行独占访问的操作系统特定的对象。WaitHandle是一个抽象类,我们一般不直接使用,而是使用它的派生类:
二、AutoResetEvent
AutoResetEvent表示线程同步事件在一个等待线程释放后收到信号时自动重置。WaitOne()方法阻塞程序执行,Set()方法释放信息。当释放后阻塞的代码继续执行。但下一次执行还需要等待信号。通俗来说,WaitOne()是关门,Set()是开门。但是开门之后,下次执行到WaitOne()的时候还需要等待开门,因为它在开门之后会自动关门。
举例,两个线程交替打印奇数和偶数:
class Program
{
static AutoResetEvent autoReset = new AutoResetEvent(false);
static void Main(string[] args)
{
Task.Run(() =>
{
for (int i = 1; i <= 100; i++)
{
if (i % 2 == 0)
{
autoReset.WaitOne();
Console.WriteLine($"我是偶数:{i}");
autoReset.Set();
}
}
});
Task.Run(() =>
{
for (int i = 1; i <= 100; i++)
{
if (i % 2 != 0)
{
Console.WriteLine($"我是奇数:{i}");
autoReset.Set();
autoReset.WaitOne();
}
}
});
Console.ReadKey();
}
原理:先打印奇数,所以在打印偶数之前需要阻塞 ,直到打印了奇数后释放。但是打印奇数释放后就不管了吗???很显然我们还要在打印了奇数后释放后再次阻塞,之后偶数打印完了才释放,这样就实现了交替打印。
注意,WaitOne()方法还有重载,有一个参数表示等待的毫秒数,如果为-1,则表示无限期等待。WaitOne()方法的返回值是一个bool类型,为true则表示等到了释放,为false则表示超过指定时间没有等待到释放。
抽象类WaitHandle还有静态方法WaitAll()和WaitAny()等,可以等待多个WaitHandle对象释放。
三、ManualResetEvent
ManualResetEvent与AutoResetEvent类似,唯一的区别就是:
AutoResetEvent在Set()之后,再次遇到WaitOne()的时候还是阻塞的。但是,如果阻塞超时后,才收到Set,那么在直接waitone的时候不会阻塞,需要重新reset。
ManualResetEvent在Set()之后,再次遇到WaitOne()的时候可直接执行。必须Reset()之后,才会阻塞。
四、Semaphore
其实.NET中的信号量(Semaphore)是操作系统维持的一个整数。当整数位0时。其他线程无法进入。当整数大于0时,线程可以进入。每当一个线程进入,整数-1,线程退出后整数+1。整数不能超过信号量的最大请求数。信号量在初始化的时候可以指定这个整数的初始值。
class Program
{
static void Main(string[] args)
{
Foo foo = new Foo();
Console.ReadKey();
}
}
public class Foo
{
Semaphore semaphore = new Semaphore(0, 3);//允许最大3个线程占用信号量 超过后就会在WaitOne那阻塞
public Foo()
{
for (int i = 0; i < 5; i++)
{
int j = i;
Task.Run(() => DoSomething(j));
}
//alling Release(3) brings the
// semaphore count back to its maximum value, and
// allows the waiting threads to enter the semaphore,
// up to three at a time
semaphore.Release(3);
}
private void DoSomething(int i)
{
semaphore.WaitOne();//当前线程占用信号量 如果信号量被占满 则阻塞
Console.WriteLine($"{i}进入信号量");
Thread.Sleep(1000 * i);
Console.WriteLine($"{i}释放信号量 信号量线程数量:{semaphore.Release()}");//当前线程释放信号量
}
}
五、SemaphoreSlim
信号量分为本地信号量(SemaphoreSlim)和系统信号量(Semaphore ),单应用程序内部同步建议使用SemaphoreSlim。
public class SemaphoreSlimTest
{
SemaphoreSlim semaphore = new SemaphoreSlim(0, 3);
public SemaphoreSlimTest()
{
for (int i = 0; i < 5; i++)
{
int j = i;
Task.Run(() => DoSomething(j));
Thread.Sleep(j * 1000);
}
//alling Release(3) brings the
// semaphore count back to its maximum value, and
// allows the waiting threads to enter the semaphore,
// up to three at a time
Thread.Sleep(500);
Console.WriteLine("Main thread calls Release(3) ");
semaphore.Release(3);
}
private void DoSomething(int i)
{
Console.WriteLine($"{i}等待进入信号量 CurrentCount:{semaphore.CurrentCount}");
semaphore.Wait();//当前线程占用信号量 如果信号量被占满 则阻塞
Console.WriteLine($"{i}进入信号量 CurrentCount:{semaphore.CurrentCount}");
Thread.Sleep(1000*i);
var count = semaphore.Release();
Console.WriteLine($"{i}释放信号量 PreviousCount:{count}");//当前线程释放信号量
}
}
六、Mutex
Mutex对象除了可以同步对受保护资源的访问,还可用于进程间同步的同步基元。例如可以使用Mutex防止应用程序打开多次。
class Program
{
static Mutex mutex = null;
static void Main(string[] args)
{
mutex = new Mutex(true, "MM", out bool createdNew);
if (createdNew)
{
Console.WriteLine("我是新开的!!!");
Console.ReadKey();
}
else
{
Console.WriteLine("已经打开了一个!!!");
Console.ReadKey();
}
}
}