zoukankan      html  css  js  c++  java
  • why happen "WaitHandles must be less than or equal to 64"

    一、背景:

          在一个项目中碰到大数据插入的问题,一次性插入20万条数据(SQL Server),并用200个线程去执行,计算需要花费多少时间,因此需要等200个线程处理完成后,记录花费的时间,需要考虑的一个问题是:如何判断判断多个线程是否全部执行完成。在执行数据库的插入过程中,当每个线程需要处理的数据量大时,是个耗时的过程,故对通过配置开启多个线程。     

    二、问题:  

    问题出来了,那么如何知道所有的线程操作都全部完成了,答案是利用C#中的的ManualResetEvent来处理;于是有下面的写法。

    //针对每个线程 绑定初始化一个ManualResetEvent实例
    ManualResetEvent doneEvent = new ManualResetEvent(false);
    //通过ThreadPool.QueueUserWorkItem(网络请求方法HttpRequest,doneEvent ) 来开启多线程
     
    //将等待事件一一加入事件列表 
    
     List<ManualResetEvent> listEvent = new List<ManualResetEvent>(); 
    for(int i=0;i<线程数;i++){
            listEvent.Add(doneEvent);
    }
     
    //主线程等待每个线程全部完成
    WaitHandle.WaitAll(listEvent.ToArray());
    //....接下去的时间计算
     
     
    //在保存数据的的每个线程中调用
    doneEvent.Set();//通知主线程 本线程保存数据方法已经调用完成 

     运行好像没有问题,但是当线程数大于64个之后抛出异常 WaitHandles must be less than or equal to 64

    通过网上查询,得知原来WaitHandle.WaitAll(listEvent.ToArray()); 这里listEvent线程数不能超过64个

    三、解决方案:

    原理:封装一个ManualResetEvent对象,一个计数器current,提供SetOne和WaitAll方法;

    主线程调用WaitAll方法使ManualResetEvent对象等待唤醒信号;

    各个子线程调用setOne方法 ,setOne每执行一次current减1,直到current等于0时表示所有子线程执行完毕 ,调用ManualResetEvent的set方法,这时主线程可以执行WaitAll之后的步骤。

    目标:减少ManualResetEvent对象的大量产生和使用的简单性。

    四、例子:

     public class MutipleThreadResetEvent : IDisposable
        {
            private readonly ManualResetEvent done;
            private readonly int total;
            private long current;
            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="total">需要等待执行的线程总数</param>
            public MutipleThreadResetEvent(int total)
            {
                this.total = total;
                current = total;
                done = new ManualResetEvent(false);
            }
    
            /// <summary>
            /// 唤醒一个等待的线程
            /// </summary>
            public void SetOne()
            {
                // Interlocked 原子操作类 ,此处将计数器减1
                if (Interlocked.Decrement(ref current) == 0)
                {
                    //当所以等待线程执行完毕时,唤醒等待的线程
                    done.Set();
                }
            }
    
            /// <summary>
            /// 等待所以线程执行完毕
            /// </summary>
            public void WaitAll()
            {
                done.WaitOne();
            }
    
            /// <summary>
            /// 释放对象占用的空间
            /// </summary>
            public void Dispose()
            {
                ((IDisposable)done).Dispose();
            }
        }

    本质就是只通过1个ManualResetEvent 对象就可以实现同步N(N可以大于64)个线程

        public class Process
        {
           public static int workItemCount = 0;
            Process() { }
            public static void ProcessDataThread(object state)
            {
                State stateinfo = state as State;
    
                int count = Int32.Parse(stateinfo.perthreadtotal.ToString());
                if (count == 0)
                {
                    ProcessManage.InputLog("输入的数据不正确,请重新输入");
                    return;
                }
                int workItemNumber = workItemCount;
                Interlocked.Increment(ref workItemCount);
                ProcessManage.InputLog(string.Format("线程{0}开始工作", workItemNumber.ToString()));
                string sql = @"insert into gpsposition (PlateType,CarNo,Latitude,Longitude,Altitude,Heading,Speed,Timestamp) 
    values";
                string insertvalue = string.Empty;
                string va = @"('02','渝B12345',10,10,10,20,10,'2016/11/1 12:00'),";
                for (int i = 0; i < count; i++)
                {
                    insertvalue = (insertvalue + va);
                }
                insertvalue = insertvalue.TrimEnd(',');        
                //执行sql语句
                try
                {
                    ProcessManage.ProcessData(string.Concat(sql, insertvalue));
                }
                catch (Exception ex)
                {
                    ProcessManage.InputLog(string.Format("线程{0},SQL执行错误:{1}", workItemNumber.ToString(), ex.Message));
                }
                finally
                {
                    ProcessManage.InputLog(string.Format("线程{0}执行完成", workItemNumber.ToString()));
                    stateinfo.manualEvent.SetOne();
                }
            }
        }

    页面调用代码如下:

     private void button1_Click(object sender, EventArgs e)
            {
                Process.workItemCount = 0;
                int threadcount = 0;
                int totalcount = 0;
                Int32.TryParse(this.txtThreadCount.Text, out threadcount);
                Int32.TryParse(this.txtTotalCount.Text, out totalcount);
                if (threadcount == 0 || totalcount == 0)
                {
                    MessageBox.Show("文本中输入的数字不正确,请输入大于0的整数");
                    return;
                }
                int perthreadtotal = totalcount / threadcount;
                ProcessManage.InputLog(string.Format("总记录数-{0}条", totalcount));
                ProcessManage.InputLog(string.Format("线程执行数量-{0}个", threadcount));
                ProcessManage.InputLog(string.Format("每个线程执行记录数-{0}条", perthreadtotal));
                ProcessManage.InputLog("=================================================");
                ProcessManage.InputLog("开始启动线程执行");
    
                State stateInfo;
                Stopwatch watch = new Stopwatch();
                watch.Start();
                using (var manualEvents = new MutipleThreadResetEvent(threadcount))
                {
                    for (int i = 0; i < threadcount; i++)
                    {
                        stateInfo = new State(manualEvents, perthreadtotal);
                        ThreadPool.QueueUserWorkItem(new WaitCallback(Process.ProcessDataThread), stateInfo);
                    }
                    manualEvents.WaitAll();
                }
                watch.Stop();
                ProcessManage.InputLog(string.Format("全部线程执行完成,耗时{0}秒",watch.Elapsed));
            }

    五、UI效果:

    总结:20万数据一次性用200个线程执行,只花费了5秒多的时间即完成。

  • 相关阅读:
    Ubuntu安装qBittorrent
    资深程序猿冒死揭开软件潜规则:无法维护的代码
    Oracle11g Active Data Guard搭建、管理
    Android 扁平化button
    Eclipse Android 代码自己主动提示功能
    Echoprint系列--编译
    一步步玩pcDuino3--mmc下的bootloader
    【Discuz】去除版权信息,标题栏与底部改动
    phoenixframe自己主动化測试平台对div弹出框(如弹出的div登陆框)的处理
    UVa
  • 原文地址:https://www.cnblogs.com/Charles2008/p/ManualResetEvent.html
Copyright © 2011-2022 走看看