惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
N
News and Events Feed by Topic
AI
AI
S
Secure Thoughts
Schneier on Security
Schneier on Security
Help Net Security
Help Net Security
N
News | PayPal Newsroom
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Hacker News: Ask HN
Hacker News: Ask HN
W
WeLiveSecurity
Google Online Security Blog
Google Online Security Blog
T
Tailwind CSS Blog
Jina AI
Jina AI
小众软件
小众软件
S
Security @ Cisco Blogs
A
About on SuperTechFans
雷峰网
雷峰网
T
Threat Research - Cisco Blogs
I
InfoQ
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Microsoft Azure Blog
Microsoft Azure Blog
AWS News Blog
AWS News Blog
The Register - Security
The Register - Security
V
Visual Studio Blog
PCI Perspectives
PCI Perspectives
Blog — PlanetScale
Blog — PlanetScale
L
LINUX DO - 最新话题
Stack Overflow Blog
Stack Overflow Blog
MongoDB | Blog
MongoDB | Blog
博客园 - 叶小钗
P
Proofpoint News Feed
美团技术团队
F
Fortinet All Blogs
NISL@THU
NISL@THU
T
Troy Hunt's Blog
U
Unit 42
博客园 - Franky
B
Blog
Webroot Blog
Webroot Blog
T
The Exploit Database - CXSecurity.com
The Hacker News
The Hacker News
宝玉的分享
宝玉的分享
Y
Y Combinator Blog
The Cloudflare Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Google DeepMind News
Google DeepMind News
P
Privacy & Cybersecurity Law Blog
Latest news
Latest news
C
Cyber Attacks, Cyber Crime and Cyber Security
GbyAI
GbyAI

博客园 - Charles Chen

.Net Framework 4.x 程序到底运行在哪个 CLR 版本之上(ZT) C#常用工具类——Excel操作类(ZT) C#程序以管理员权限运行(ZT) fsn文件解析(C#) MS SQL执行大脚本文件时,提示“内存不足”的解决办法() OLDB读取excel的数据类型不匹配的解决方案(ZT) 加密配置文件(App.Config和Web.config)中connectionStrings通用方法 WinForm中使用XML文件存储用户配置及操作本地Config配置文件(zt) 理解DllImportAttribute中的属性 Winform中消息循环、异步操作、Control.Invoke&Control.BeginInvoke学习 Winform打包程序制作的快捷方式指向错误的位置(指向安装包文件) Reflector导出.NET工程项目的修复 存储过程返回值及输出参数笔记 使用本地系统帐户和域用户帐户两者区别(microsoft SQLServer2000)(ZT) SQL字符串的分组聚合(ZT) SQL中使用update inner join和delete inner join (ZT) C# ComboBox(DropDownList)数据绑定后,怎样再添加选项(ZT) 项目维护之WinXP IIS中HTTP500的来龙去脉 一次项目维护案例而对事务学习的笔记
why happen "WaitHandles must be less than or equal to 64"
Charles Chen · 2016-11-14 · via 博客园 - Charles Chen

一、背景:

      在一个项目中碰到大数据插入的问题,一次性插入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秒多的时间即完成。