zoukankan      html  css  js  c++  java
  • (译)构建Async同步基元,Part 2 AsyncAutoResetEvent

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

    最近在学习.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 2 AsyncAutoResetEvent

             上一篇,我们构建了async版本的ManualResetEvent。现在,让我们构建一个async版本的AutoResetEvent

             ManualResetEvent对象,当调用其Set()方法时,ManualResetEvent转化为有信号状态,并持有此信号直到显示调用其Reset()方法重置为无信号状态,调用Reset()重置为无信号状态导致下一次WaitOne()等待信号。相比之下,AutoResetEvent也在调用其Set()方法时获得信号,但是其在调用完WaitOne()后自动重置为无信号状态。比如,如果ManualResetEvent上有4个等待线程,当其中一个线程调用set()方法后,则4个等待线程都将完成。相比之下,如果AutoResetEvent上有4个等待线程,当其中一个线程调用set()方法后,仅仅是其中一个线程获得信号,其他3WaitHandle依然为无信号状态(需要注意的是AutoResetEvent常常会导致无法跟踪信号的到达。比如:起初没有线程等待,事件接收了两次set()信号,然后两个线程再在事件上等待信号,这时只有其中一个完成等待---可以参考示例m_signaled变量的实现)

             这是我们将构建的目标类型:

    public class AsyncAutoResetEvent
    { 
        public Task WaitAsync(); 
        public void Set(); 
    }

    首先,我们需要一些成员(egTaskCompletionSource<TResult>)。上一篇中的AsyncManualResetEvent,我们只需要单个TaskCompletionSource<TResult>实例,因为事件的通知会唤醒当前所有等待。但是AsyncAutoResetEvent不一样,我们需要区别对待不同的等待,因为如果有多个线程等待,一个信号到达只能唤醒一个等待。所以,我们需要一个TaskCompletionSource<TResult>实例集合。此外,信号到达时可能没有等待者,所以我们需要使用一个bool变量进行跟踪。最后,在一些情况下我们能重用已完成的Task来提高性能,所以我将一直保持其中一个引用,设计如下:

    private readonly static Task s_completed = Task.FromResult(true); 
    private readonly Queue<TaskCompletionSource<bool> m_waits
                        = new Queue<TaskCompletionSource<bool>(); 
    private bool m_signaled;

    接下来,让我们实现一个命名为WaitAsync()的方法。当调用WaitAsync()时,如果m_signaledtrue,我们直接返回已经完成的s_completed,因为这个等待调用消费这个信号,所以我们需要将m_signaled重置为false。如果m_signaledfalse,我们将创建一个TaskCompletionSource<TResult>实例插入m_waits队列中,并且返回实例对应的Task。这个Task将推迟完成直到有线程调用TaskSet()方法并且唤醒等待者。但是要注意,这里需要保持多个操作执行的原子性,因此,我对m_waits队列加锁以确保同步。

    public Task WaitAsync() 
    { 
        lock (m_waits) 
        { 
            if (m_signaled) 
            { 
                m_signaled = false; 
                returns_completed; 
            } 
            else
            { 
                var tcs = new TaskCompletionSource<bool>(); 
                m_waits.Enqueue(tcs); 
                return tcs.Task; 
            } 
        } 
    }

    接下来,我将实现Set()方法。Set()方法将首先检查m_waits队列中是否有等待者。如果有,则从队列中取出一个TaskCompletionSource<bool>并且完成它。如果m_waits队列为空,则只是简单的将m_signaled设置为true。这里的操作需要保持原子性,并且要与WaitAsync()方法保持同步,所以set()的主体代码需要再一次对m_waits队列加锁。这里要注意一个重要的事情,在之前的文章中,我讨论了TaskCompletionSource<TResult> [Try]Set*() 系列方法,会使TaskCompletionSource<TResult>对应的Task作为同步调用的一部分运行。如果我们在lock内部调用SetResilt(),则Task的同步延续的运行将长时间持有lock。因此,我们释放lock后再调用Task[Try]Set*() 系列方法来完成任务。

    public void Set() 
    { 
        TaskCompletionSource<bool> toRelease = null
        lock (m_waits)
        { 
            if (m_waits.Count> 0) 
                toRelease = m_waits.Dequeue(); 
            else if (!m_signaled) 
                m_signaled = true; 
        } 
        if (toRelease != null)  
            toRelease.SetResult(true); 
    }

    这就是本节要讲的AsyncAutoResetEvent

    完整源码如下:

        public class AsyncAutoResetEvent
        {
            // 保存一个成功完成的 Task<TResult>,供重用以提高性能
            private readonly static Task s_completed = Task.FromResult(true);
            // 等待任务队列
            private readonly Queue<TaskCompletionSource<bool>> m_waits = new Queue<TaskCompletionSource<bool>>();
            // 用于跟踪 信号到达时可能没有等待者 的情况,将AsyncAutoResetEvent的初始状态设置为有信号
            private bool m_signaled;
    
            public Task WaitAsync()
            {
                lock (m_waits)
                {
                    if (m_signaled)
                    {
                        m_signaled = false;
                        return s_completed;
                    }
                    else
                    {
                        var tcs = new TaskCompletionSource<bool>();
                        m_waits.Enqueue(tcs);
                        return tcs.Task;
                    }
                }
            }
    
            public void Set()
            {
                TaskCompletionSource<bool> toRelease = null;
                lock (m_waits)
                {
                    if (m_waits.Count > 0)
                        toRelease = m_waits.Dequeue();
                    else if (!m_signaled)
                        m_signaled = true;
                }
                if (toRelease != null)
                    toRelease.SetResult(true);
            }
        }

    下一节,我将实现一个async版本的CountdownEvent

     

     

    推荐阅读:

                       异步编程:同步基元对象(上)

                       异步编程:同步基元对象(下)

     

    感谢你的观看……

    原文:Building Async Coordination Primitives, Part 2: AsyncAutoResetEvent

    作者:Stephen Toub – MSFT

     

  • 相关阅读:
    C#重载操作符的那点事
    Winform最小化托盘
    C#多线程传参
    GridView事件大全
    测试2
    测试
    FindFileByContent
    JavaScriptCallCSharp js调用C#属性与方法
    GroupBy
    查询指定属性的文件
  • 原文地址:https://www.cnblogs.com/heyuquan/p/2860112.html
Copyright © 2011-2022 走看看