一、背景:

在一个项目中碰到大数据插入的问题,一次性插入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. 将DataTable 导出为csv

    //测试用的 导出查询的DataTable public static void Export(DataTable dtResult) { string strHeader =null; string ...

  2. Cruehead.1

    查壳   没有 我拖 alt+F9 到上面        入口处   下断 关键跳      略过   就没了 要实现 强暴  直接过... 仔细来看看... 那两个调用   都下断   看看  判断 ...

  3. 解决php deprecated 的问题

    Deprecated :意思是“不推荐” php 5.3 从一方面来讲,可以说在07年计划PHP6的中的一个pre版本,增加了很多功能,统一了很多语法,使PHP变得更加强大与简洁. 说到统计架构规划, ...

  4. div 在页面上漂浮

    <div id="codefans_net" style="position:absolute;z-index:5;">        <a& ...

  5. devenv命令详解

    devenv  [解决方案文件 | 项目文件 | 任意文件.扩展名]  [开关] devenv 的第一个参数通常是一个解决方案文件或项目文件. 如果您希望在编辑器中自动打开文件, 也可以使用任何其他文 ...

  6. 多表关联 update

    UPDATE t_invests INNER JOIN t_user_coupons ON t_invests.user_coupon_id = t_user_coupons.id SET t_inv ...

  7. 在 Windows 下部署 Vagrant

    Vagrant 是一个可创建轻便.可复用的虚拟开发环境的跨平台工具,通过打包分可使所有开发成员使用统一的开发环境. 下面是我自己记录配置 Vagrant 的过程. 1.安装 VirtualBox 略. ...

  8. A Look At Android Support Annotations

    转自:https://asce1885.gitbooks.io/android-rd-senior-advanced/content/shen_ru_qian_chu_android_support_ ...

  9. 测试cookie的读写

    js文件使用utf8编码 <p><img id="img" onclick="javascript:var s=document.createEleme ...

  10. 新冲刺Sprint3(第四天)

    一.Sprint介绍 实现了SQLite数据库记录自动登录.注销功能 真机测试效果图:     二.Sprint周期 看板: 燃尽图: