zoukankan      html  css  js  c++  java
  • 异步线程及同步控制

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

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

    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重置为阻塞,使用者因此又在等待制造者加入项,只要制造者一加入新项,又有信号给使用者,又重新开始工作,周而复始,解决了不同线程的同步控制了。在本单位的很多项目中,为了改进用户的体验,在很多地方使用些做法(更多的是放弃中间的所有操作,只要应用在用户对一个操作连续点击,到停止时其实只显示最后一个操作就行)。

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

  • 相关阅读:
    Guid ToString 格式
    SQL Server 自增字段归零
    一些被触动的话
    【简易教程】在网站上养一只萌咔咔的小仓鼠
    SQL分页语句
    WPF使用System.Windows.SystemParameters类获得屏幕分辨率
    WPF编程学习——窗口
    C# .net WPF无边框移动窗体
    WPF 4 Ribbon 开发 之 快捷工具栏(Quick Access Toolbar)
    转 遗传算法简介
  • 原文地址:https://www.cnblogs.com/Yjianyong/p/2632310.html
Copyright © 2011-2022 走看看