异步线程及同步控制

    在日常开发中,很多耗时的工作都是使用异步线程的完成,特别的在C/S模式中更显常见,但很多时候有涉及到资源的独占,对资源的锁定有多种做法,很多时候引起死锁及锁定失效,这些有MSDN中都有相当多的介绍,我就不再献丑了。这里主要是在一个做考勤机的模块中,对MSDN的制造都和使用都进行同步控制的改进及应用。

    参考MSDN的主题:《如何:对制造者线程和使用者线程进行同步(C# 编程指南)》

ExpandedBlockStart.gif MSDN Code
using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;

public  class SyncEvents
{
     public SyncEvents()
    {

        _newItemEvent =  new AutoResetEvent( false);
        _exitThreadEvent =  new ManualResetEvent( false);
        _eventArray =  new WaitHandle[ 2];
        _eventArray[ 0] = _newItemEvent;
        _eventArray[ 1] = _exitThreadEvent;
    }

     public EventWaitHandle ExitThreadEvent
    {
         get {  return _exitThreadEvent; }
    }
     public EventWaitHandle NewItemEvent
    {
         get {  return _newItemEvent; }
    }
     public WaitHandle[] EventArray
    {
         get {  return _eventArray; }
    }

     private EventWaitHandle _newItemEvent;
     private EventWaitHandle _exitThreadEvent;
     private WaitHandle[] _eventArray;
}
public  class Producer 
{
     public Producer(Queue< int> q, SyncEvents e)
    {
        _queue = q;
        _syncEvents = e;
    }
     //  Producer.ThreadRun
     public  void ThreadRun()
    {
         int count =  0;
        Random r =  new Random();
         while (!_syncEvents.ExitThreadEvent.WaitOne( 0false))
        {
             lock (((ICollection)_queue).SyncRoot)
            {
                 while (_queue.Count <  20)
                {
                    _queue.Enqueue(r.Next( 0, 100));
                    _syncEvents.NewItemEvent.Set();
                    count++;
                }
            }
        }
        Console.WriteLine( " Producer thread: produced {0} items ", count);
    }
     private Queue< int> _queue;
     private SyncEvents _syncEvents;
}

public  class Consumer
{
     public Consumer(Queue< int> q, SyncEvents e)
    {
        _queue = q;
        _syncEvents = e;
    }
     //  Consumer.ThreadRun
     public  void ThreadRun()
    {
         int count =  0;
         while (WaitHandle.WaitAny(_syncEvents.EventArray) !=  1)
        {
             lock (((ICollection)_queue).SyncRoot)
            {
                 int item = _queue.Dequeue();
            }
            count++;
        } 
        Console.WriteLine( " Consumer Thread: consumed {0} items ", count);
    }
     private Queue< int> _queue;
     private SyncEvents _syncEvents;
}

public  class ThreadSyncSample
{
     private  static  void ShowQueueContents(Queue< int> q)
    {
         lock (((ICollection)q).SyncRoot)
        {
             foreach ( int item  in q)
            {
                Console.Write( " {0}  ", item);
            }
        }
        Console.WriteLine();
    }

     static  void Main()
    {
        Queue< int> queue =  new Queue< int>();
        SyncEvents syncEvents =  new SyncEvents();

        Console.WriteLine( " Configuring worker threads... ");
        Producer producer =  new Producer(queue, syncEvents);
        Consumer consumer =  new Consumer(queue, syncEvents);
        Thread producerThread =  new Thread(producer.ThreadRun);
        Thread consumerThread =  new Thread(consumer.ThreadRun);

        Console.WriteLine( " Launching producer and consumer threads... ");        
        producerThread.Start();
        consumerThread.Start();

         for ( int i= 0; i< 4; i++)
        {
            Thread.Sleep( 2500);
            ShowQueueContents(queue);
        }

        Console.WriteLine( " Signaling threads to terminate... ");
        syncEvents.ExitThreadEvent.Set();

        producerThread.Join();
        consumerThread.Join();
    }

}

    考勤机业务描述:通过一个Windows Services来同时监听多台考勤机。由于考勤机底层SDK的问题,各种操作不能同时操作,特别是不同的考勤机的不同的操作也会引起内存的访问失败,(可能是考勤机底层SDK没有考虑到多台操作,对部分同用的内存处理不好引起的,这不是我们应该公司的问题,厂家也不可能一下子修复这些问题,只有我们想办法了),因些想到用一个线程去处理多个考勤机的动作,由于Windows Services没有消息泵,在些用一个循环线程来维持底层的状态(底层的SDK只能做由创建的线程来调用其方法),对于不同考勤机所要处理的动作,全部都放在一个队列Queue中,由循环线程来逐个执行。此做法效率虽然低了点,可以对于迸发不是很多的业务,却足够了,又能很好解决项目进度的问题。

    在MSDN的示例中,消费者是可以使用新线程却工作,所以

      while (WaitHandle.WaitAny(_syncEvents.EventArray) != 1)
        {
            lock (((ICollection)_queue).SyncRoot)
            {
                int item = _queue.Dequeue();
            }
            count++;
        } 

代码int item = _queue.Dequeue();是没有问题的,好像在考勤机中,只能一个线程操作,而且_queue.Dequeue后的Do something耗时比较大,(不能新开线程),该做法就完成不可行了,如果耗时过长,就会对制造都进行了阻塞,不合要求;而且在SyncEvents中使用AutoResetEvent,当WaitHandle.WaitAny(_syncEvents.EventArray) 收到信号时就会自动恢复,制造者一往Queue中加入项,又马上有新的信号,起不了同步的作用,因此,把AutoResetEvent改为ManualResetEvent,当Do Something完成后,而与队列没有项时,才重置制造者的信号

///   <summary>
    
///  同步事件控制类。参考MSDN的对制造者线程和使用者线程进行同步(C# 编程指南)
    
///   </summary>
    
///   <typeparam name="T"> T 只能为 AutoResetEvent 或 ManualResetEvent </typeparam>
     public  class SyncEvents<T>  where T : WaitHandle
    {
         public SyncEvents()
        {
             // _newItemEvent = new AutoResetEvent(false);
             if ( typeof(T) ==  typeof(AutoResetEvent))
                _newItemEvent =  new AutoResetEvent( false);
             else  if ( typeof(T) ==  typeof(ManualResetEvent))
                _newItemEvent =  new ManualResetEvent( false);

            _exitThreadEvent =  new ManualResetEvent( false);
            _eventArray =  new WaitHandle[ 2];
            _eventArray[ 0] = _newItemEvent;
            _eventArray[ 1] = _exitThreadEvent;
        }

         ///   <summary>
        
///  退出线程事件。为ManualResetEvent类。
        
///   </summary>
         public EventWaitHandle ExitThreadEvent
        {
             get {  return _exitThreadEvent; }
        }
         ///   <summary>
        
///  新项的消息。为AutoResetEvent类
        
///   </summary>
         public EventWaitHandle NewItemEvent
        {
             get {  return _newItemEvent; }
        }
         ///   <summary>
        
///  事件数组。EventArray[0]为NewItemEvent,NewItemEvent[1]ExitThreadEvent
        
///   </summary>
         public WaitHandle[] EventArray
        {
             get {  return _eventArray; }
        }

         private EventWaitHandle _newItemEvent;
         private EventWaitHandle _exitThreadEvent;
         private WaitHandle[] _eventArray;
    }

 初始化相关实体。

_queue = new Queue<EventsActionEntry>();
 _syncEvents = new SyncEvents<ManualResetEvent>();

m_WorkThread = new Thread(new ThreadStart(CreateEnrollListener));
m_WorkThread.Start();

在实体应用中,制造都不用考虑如果退出,只要是对队列里加进项,并发出信号就可以了

            Console.WriteLine("Receive a message wait for process...");
            lock (((ICollection)_queue).SyncRoot)
            {
                EventsActionEntry entry = new EventsActionEntry { MachineNumber = e.ReceiveData.MachineID, InvokeActionType = ActionType.LogTime};     //生成Action的类型

                _queue.Enqueue(entry);
                _syncEvents.NewItemEvent.Set();
            }

这个MSDN的做法没有多在变动。

EventsActionEntry 实体的设计是以为使用者执行而设计,在很多项目中有一个这样的现象,当第一个操作很耗时,而队列的项不断增加,而中间的项又没有用了,只需要执行最后一个项,我们可以在lock内对entry的标识进行更改,而在使用者取出entry时,对标识进行判断,这样就可以放弃中间的所有Action的执行。

在使用者的执行方法m_WorkThread = new Thread(new ThreadStart(CreateEnrollListener));   CreateEnrollListener中,我是这样使用的

            while (WaitHandle.WaitAny(_syncEvents.EventArray) != 1)
            {
                EventsActionEntry actionItem = null;
                lock (((ICollection)_queue).SyncRoot)
                {
                    if (_queue.Count > 0)
                        actionItem = _queue.Dequeue();
                }

                //处理事件
                ProcessEventAction(actionItem);

                lock (((ICollection)_queue).SyncRoot)
                {
                    if (_queue.Count == 0)
                    {
                        _syncEvents.NewItemEvent.Reset();
                    }
                }
            }

ProcessEventAction(actionItem); 在第一个lock的外面,这样,就不会阻塞制造者往队列里加项。这时,NewItemEvent处理非阻塞状态,因此可以一直循环执行完队列的所有Action,到所有项完成后,第二个lock对NewItemEvent重置为阻塞,使用者因此又在等待制造者加入项,只要制造者一加入新项,又有信号给使用者,又重新开始工作,周而复始,解决了不同线程的同步控制了。在本单位的很多项目中,为了改进用户的体验,在很多地方使用些做法(更多的是放弃中间的所有操作,只要应用在用户对一个操作连续点击,到停止时其实只显示最后一个操作就行)。

    有些表述可能有误和不清晰,希望大家能得到更好的应用,对能改进的地方,多多发言,并提出意见。

转载于:https://www.cnblogs.com/Yjianyong/archive/2012/08/10/2632310.html

猜你喜欢

转载自blog.csdn.net/weixin_34370347/article/details/94199617
今日推荐