传送门:异步编程系列目录……
最近在学习.NET4.5关于“并行任务”的使用。“并行任务”有自己的同步机制,没有显示给出类似如旧版本的:事件等待句柄、信号量、lock、ReaderWriterLock……等同步基元对象,但我们可以沿溪这一编程习惯,那么这系列翻译就是给“并行任务”封装同步基元对象。翻译资源来源《(译)关于Async与Await的FAQ》
1. 构建Async同步基元,Part 1 AsyncManualResetEvent
2. 构建Async同步基元,Part 2 AsyncAutoResetEvent
3. 构建Async同步基元,Part 3 AsyncCountdownEvent
4. 构建Async同步基元,Part 4 AsyncBarrier
5. 构建Async同步基元,Part 5 AsyncSemaphore
6. 构建Async同步基元,Part 6 AsyncLock
7. 构建Async同步基元,Part 7 AsyncReaderWriterLock
源码:构建Async同步基元.rar
开始:构建Async同步基元,Part 6 AsyncLock
最近,我们刚刚构建了一个AsyncSemphore,现在,我们来构建一个在using块中支持异步互斥机制的类型。
正如前面帖子所述,信号量非常适用于限流及资源访问管理。你可以给信号量一个初始计数,然后它将只允许指定数量的消费者成功获得信号,强迫多余的请求等待直到资源被释放内部信号计数大于0。信号量可以用来保护正确进入特定代码区域,并且内部计数可以设置为1。通过这种方式,你能使用信号量来达到互斥目的,比如:
1 2 3 4 5 6 7 8 9 10 11 | private readonly AsyncSemaphore m_lock = new AsyncSemaphore(1); … await m_lock.WaitAsync(); try { … // protected code here } finally { m_lock.Release(); } |
我们可以通过创建一个AsyncLock类型来支持使用using关键字来简化编码。我们的目的是和上面片段一致的,但是通过类似下面的代码来实现:
1 2 3 4 5 6 | private readonly AsyncLock m_lock = new AsyncLock(); … using ( var releaser = await m_lock.LockAsync()) { … // protected code here } |
为了达到这样的效果,我们将构建下面这样的类型:
1 2 3 4 5 6 7 8 9 | public class AsyncLock { public AsyncLock(); public Task<Releaser> LockAsync(); public struct Releaser : IDisposable { public void Dispose(); } } |
在内部,我们将维护两个成员。我们使用AsyncSemaphore对象来处理大部分逻辑,使用m_releaser变量缓存的Task<Releaser>实例,当访问的锁没有竞争时,可以直接返回从而避免不必要的分配。
1 2 | private readonly AsyncSemaphore m_semaphore; private readonly Task<Releaser> m_releaser; |
Releaser结构仅仅是实现了IDisposable接口的Dispose()方法,该方法将释放底层的信号量。这是允许我们在using块中使用Releaser构造的原因,由using自动生成的finally块将调用Dispose()方法,即Seamphore实例的Release()方法来进行释放。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public struct Releaser : IDisposable { private readonly AsyncLock m_toRelease; internal Releaser(AsyncLock toRelease) { m_toRelease = toRelease; } public void Dispose() { if (m_toRelease != null ) m_toRelease.m_semaphore.Release(); } } |
我们的AsyncLock的构造函数仅仅是初始化成员,使用数值为1的参数创建一个Semaphore,并且创建一个缓存的Task<Releaser>。
1 2 3 4 5 | public AsyncLock() { m_semaphore = new AsyncSemaphore(1); m_releaser = Task.FromResult( new Releaser( this )); } |
最后,构建我们的LockAsync方法。我们首先调用Semaphore的WaitAsync()获得一个Task,用于指代我们获得的锁。如果Task已经完成,则同步返回缓存的Task<Releaser>,这也意味着如果锁没有竞争就不需要分配。如果Task没有完成,则返回一个Task<Releaser>的延续任务。
1 2 3 4 5 6 7 8 9 | public Task<Releaser> LockAsync() { var wait = m_semaphore.WaitAsync(); return wait.IsCompleted ? m_releaser : wait.ContinueWith((_,state) => new Releaser((AsyncLock)state), this , CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } |
这就是本节要讲的AsyncLock。
完整源码如下:
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | public class AsyncLock { private readonly AsyncSemaphore m_semaphore; // 缓存Task<Releaser>实例,当访问的锁没有竞争时,可以直接返回从而避免不必要的分配。 private readonly Task<Releaser> m_releaser; public AsyncLock() { // 信号量为1,用于实现互斥 m_semaphore = new AsyncSemaphore(1); m_releaser = Task.FromResult( new Releaser( this )); } public Task<Releaser> LockAsync() { var wait = m_semaphore.WaitAsync(); return wait.IsCompleted ? m_releaser : wait.ContinueWith((_, state) => new Releaser((AsyncLock)state) , this , CancellationToken.None , TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } public struct Releaser : IDisposable { private readonly AsyncLock m_toRelease; internal Releaser(AsyncLock toRelease) { m_toRelease = toRelease; } // using块生成的finally块调用IDisposable接口的Dispose()方法 public void Dispose() { if (m_toRelease != null ) m_toRelease.m_semaphore.Release(); } } } /// <summary> /// 使用方式 /// </summary> public class AsyncLock_Test { private readonly AsyncLock m_lock = new AsyncLock(); public async void Test() { using ( var releaser = await m_lock.LockAsync()) { // 限制访问代码 } } } |
下一节,我将实现一个异步版本的 read/writer 锁。
推荐阅读:
异步编程:同步基元对象(上)
异步编程:同步基元对象(下)
感谢你的观看……
原文:《Building Async Coordination Primitives, Part 6: AsyncLock》