一、背景:

在一个项目中碰到大数据插入的问题,一次性插入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) == )
{
//当所以等待线程执行完毕时,唤醒等待的线程
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 = ;
Process() { }
public static void ProcessDataThread(object state)
{
State stateinfo = state as State; int count = Int32.Parse(stateinfo.perthreadtotal.ToString());
if (count == )
{
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 = ; 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 = ;
int threadcount = ;
int totalcount = ;
Int32.TryParse(this.txtThreadCount.Text, out threadcount);
Int32.TryParse(this.txtTotalCount.Text, out totalcount);
if (threadcount == || totalcount == )
{
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 = ; 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秒多的时间即完成。

why happen "WaitHandles must be less than or equal to 64"的更多相关文章

  1. Reloading Java Classes 201: How do ClassLoader leaks happen? Translation

    The original link : http://zeroturnaround.com/rebellabs/rjc201/ From ClassLoaders to Classes 从ClassL ...

  2. English trip V1 - 6.Accidents Happen! 发生意外! Teacher:Corrine Key: 过去进行时 was or were + Ving

    In this lesson you will learn to talk about past occurences. 过去进行时 课上内容(Lesson) C: Hi, Loki! L: Hi, ...

  3. 【MyEcplise】导入项目后,会定时弹出一下错误MyEcplise tern was unable to complete your request in time.This couble happen if your project contains several large javaScript libraies.

    Myecplise弹出错误如下: 错误代码: MyEcplise tern was unable to complete your request in time.This couble happen ...

  4. happen before 原则

    并发一直都是程序开发者绕不开的难题,在上一篇文章中我们知道了导致并发问题的源头是 : 多核 CPU 缓存导致程序的可见性问题.多线程间切换带来的原子性问题以及编译优化带来的顺序性问题. 原子性问题我们 ...

  5. jvm(三)指令重排 & 内存屏障 & 可见性 & volatile & happen before

    参考文档: https://tech.meituan.com/java-memory-reordering.html http://0xffffff.org/2017/02/21/40-atomic- ...

  6. 【JS】Beginner1:Making Stuff Happen

    1.JS(JavaScript) is for interactivity 2.How does JS relate to HTML&CSS? script tag script elemen ...

  7. In ZeroDB, the client is responsible for the database logic. Data encryption, decryption, and compression also happen client side. Therefore, the server never has any knowledge about the data, its str

    zerodb/index.rst at master · zerodb/zerodb https://github.com/zerodb/zerodb/blob/master/docs/source/ ...

  8. 进程间IPC通信-stop waiting for thing to happen,go out and make them happen!!!

    进程间通信: System V IPC对象: ipcs -q:查看消息队列   ipcs -m:查看共享内存 ipcs -s:查看信号灯集 ipcrm -q:删除消息队列   ipcrm -m:删除共 ...

  9. 从netty-example分析Netty组件续

    上文我们从netty-example的Discard服务器端示例分析了netty的组件,今天我们从另一个简单的示例Echo客户端分析一下上个示例中没有出现的netty组件. 1. 服务端的连接处理,读 ...

随机推荐

  1. 操作系统学习笔记(五)--CPU调度

    由于第四章线程的介绍没有上传视频,故之后看书来补. 最近开始学习操作系统原理这门课程,特将学习笔记整理成技术博客的形式发表,希望能给大家的操作系统学习带来帮助.同时盼望大家能对文章评论,大家一起多多交 ...

  2. WebStorm里面配置运行React Native的方案

    以前开发react native项目总是需要打开WebStorm编写代码,Xcode跑项目.显得有点多余. 今天教大家如何直接使用WebStorm这个IDE直接完成编码+运行项目工作.这样就可以不用打 ...

  3. 连接WCF报EntityFramework.SqlServer 错误的解决方法

    现象: The Entity Framework provider type 'System.Data.Entity.SqlServer.SqlProviderServices, EntityFram ...

  4. Html概要及示例(一)

    Html 简介 Html 是一种超文本标记语言,标记往往成对出现,例如 段落标记<p>  层标记 <div> 以及 <marqueen>等等,标记繁多 但未必就要全 ...

  5. Init

    alloc负责分配对象空间,init负责初始化对象.init是实例方法,返回的是初始化后的对象的地址.init是NSObject的初始化方法. 子类不实现init,会执行由NSObject定义的ini ...

  6. secureCRT的一些小知识

    secureCRT 是一个非常不错的终端软件,在嵌入式开发过程中经常使用到,所以了解一下其快捷键操作是非常有必要的,可以提高开发效率. 0.在secureCRT里切换不同的窗口:ctrl+tab.   ...

  7. 关于分开编写多个LaTeX文件的一点微小的总结

    [转载请注明出处]http://www.cnblogs.com/mashiqi 2016/11/05 在编写LaTeX文档的时候,由于文档的section较多,或者section的编写时间各不相同,我 ...

  8. 排球比赛计分规则(P205页)

    排球比赛计分规则: 1.SPEC的目标是什么?SPEC的目标不包括什么? 为了让大家更加了解排球比赛. 2.SPEC用户和典型场景是什么? 用户:运动员,观众,教练 场景:排球赛场 3.SPEC用到哪 ...

  9. ImageLoader框架的使用、调用系统相册显示图片并裁剪显示、保存图片的两种方式

    ImageLoader虽然说是一个相对于比较老的一个框架了 ,但是总的来说,还是比较好用的,今天我就总结了一下它的用法.还有调用系统相册并裁剪,以及,通过sharedpreference和文件存储来保 ...

  10. js 基础面试题

    function printArray(arr){ for(var i in arr){ if(arr[i] instance of Array){ printArray(arr[i]); }else ...