随笔 - 75  文章 - 0  评论 - 2474  阅读 - 141万

(译)构建Async同步基元,Part 6 AsyncLock

传送门:异步编程系列目录……

最近在学习.NET4.5关于“并行任务”的使用。“并行任务”有自己的同步机制,没有显示给出类似如旧版本的:事件等待句柄、信号量、lockReaderWriterLock……等同步基元对象,但我们可以沿溪这一编程习惯,那么这系列翻译就是给“并行任务”封装同步基元对象。翻译资源来源《(译)关于AsyncAwaitFAQ

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方法。我们首先调用SemaphoreWaitAsync()获得一个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

作者:Stephen Toub – MSFT

 

posted on   滴答的雨  阅读(3091)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· SQL Server 2025 AI相关能力初探
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库

点击右上角即可分享
微信分享提示