Semaphore大家应该都很熟悉,一个能够指定最大并发个数的同步锁。
实际上.NET还有一个轻量级的信号量实现SemaphoreSlim,其不但能够更加高效的实现信号量的功能,还提供了一个异步等待的API。
我们首先回忆下SemaphoreSlim同步等待时的情况。
class Program
{
private static SemaphoreSlim semaphore;
static void Main(string[] args)
{
semaphore = new SemaphoreSlim(1, 1);
var tasks = new Task[2];
for (int i = 0; i <= 1; i++)
{
tasks[i] = Task.Run(() =>
{
Console.WriteLine("Task {0}等待信号量",
Task.CurrentId);
semaphore.Wait();
Console.WriteLine("Task {0} 获得信号量.", Task.CurrentId);
//模拟做事情况
Thread.Sleep(1000);
Console.WriteLine("Task {0} 释放信号量; 释放前数目: {1}.",
Task.CurrentId, semaphore.Release());
});
}
// 等待task执行完成.
Task.WaitAll(tasks);
Console.ReadLine();
}
}
如上所示的代码中,SemaphoreSlim的初始信号数是1,且同时只能有一个线程获得锁。
此时我们的代码会依次执行。
但是此时task1和task2 的线程是阻塞的。那么在UI线程上使用,尤其是WPF这种单线程应用来说,几乎是不可行的。
此时我们就可以使用今天的主角WaitAsync
我们稍稍修改下代码
class Program
{
private static SemaphoreSlim semaphore;
static void Main(string[] args)
{
semaphore = new SemaphoreSlim(, );
var tasks = new Task[];
for (int i = ; i <= ; i++)
{
tasks[i] = Task.Run(async () =>
{
var currentId = Task.CurrentId;
Console.WriteLine("Task {0}等待信号量",
currentId);
Foo(currentId);
Console.WriteLine("Task {0} 执行非同步区代码.", currentId);
Thread.Sleep();
Console.WriteLine("Task {0} 非同步区代码执行完成.", currentId);
});
}
// 等待task执行完成.
Task.WaitAll(tasks);
Console.ReadLine();
}
public static async Task Foo(int? id)
{
var currentId = id;
await semaphore.WaitAsync();
Console.WriteLine("Task {0} 获得信号量.", currentId);
//模拟做事情况
Console.WriteLine("Task {0} 执行同步区代码.", currentId);
Thread.Sleep();
Console.WriteLine("Task {0} 同步区代码执行完成.", currentId);
Console.WriteLine("Task {0} 释放信号量; 释放前数目: {1}.",
currentId, semaphore.Release());
}
}
我们将同步区执行的代码抽取到方法Foo中,而内部调用WaitAsync
。
结果如下所示
我们看到task3在没有获得同步锁的情况下WaitAsync
直接返回线程控制权,所以task3线程没有被阻塞能够优先执行非同步区代码。而在获得信号量之后,继续执行同步区代码。
参考文档:
本文会经常更新,请阅读原文: https://xinyuehtx.github.io/post/%E4%BD%BF%E7%94%A8SemaphoreSlim%E5%AE%9E%E7%8E%B0%E5%BC%82%E6%AD%A5%E7%AD%89%E5%BE%85.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。