多线程遍历Datatable,取到行数据后,每个线程进行一个耗时操作
private void button2_Click(object sender, EventArgs e) { listBox1.Items.Clear(); Thread[] threads = new Thread[15]; for (int i = 0; i < threads.Length; i++) { threads[i] = new Thread(new ThreadStart(Run)); threads[i].Start(); } } private void Run() { foreach (DataRow dataRow in m_DataTable.Rows) { if ((int)dataRow["Type"] == 0) { lock (m_DataTable) { dataRow["Type"] = 1; } string email = dataRow["Email"].ToString(); // 取得email后,执行耗时操作 } } }
我猜测,楼主大概是这种代码不会去写,那我说一下大概想路,首先需要一个函数去调度各线程所需要读取的数据。当没有数据可读取时,线程休眠。或者采取一个更为简单的策略:线程1读取datatable中第15*N+0行进行处理,线程2读取datatable中第15*N+1行,……没有数据就休眠。其他的,楼主的代码应该可以用。
想过这个,感觉不妥,我不只是遍历,如果取得行数据进行一个耗时操作没成功,还要继续等下一轮来做
@lanmiao: 你可以实现一个调度的函数,主要就是实现一个处理的队列,把需要处理的行号放入队列中去。在处理开始是,从队列中请求一个行号,然后去处理,如果出错,就将该行号插入到队列尾部去。当然了,这种方法的缺陷在于,队列中保存的是行号而不是数据,datatable不能做更新处理。最好的方法是,在队列中直接保存数据,也就是说读取数据的这个操作不放入到多线程中去做。这样的话,可随时更新数据,可随时处理。延伸一下就是,开启多少线程去处理,也可以放入调度中去做了。
@sinhbv: 能详细点么
@lanmiao:
public class DataQueue { public DataQueue() { queue = new Queue<Data>(); } public void OperateFun() { Data item = getData(); if (!workFun(item)) { addData(item); } } public void ThreadStart() { //todo: } private void addData(Data item) { queue.Enqueue(item); } private Data getData() { return queue.Dequeue(); } private bool workFun(Data data) { //todo: return true; } private Queue<Data> queue; } public class Data { public string MissionData { set; get; } }
大概就是这个样子。
感觉你补充的代码没什么问题的,但run函数最好这样修改:
private void Run() { foreach (DataRow dataRow in m_DataTable.Rows) { if((int)dataRow["Type"] != 0) { continue; } lock (m_DataTable) { if ((int)dataRow["Type"] != 0) { continue; } dataRow["Type"] = 1; } string email = dataRow["Email"].ToString(); // 取得email后,执行耗时操作 } }
我按你这么做了,暂时没出现问题,能解释下么 或者会不会存在隐患(漏读、重读)
@lanmiao: 我的这个方案就是解决了一些漏洞或隐患。
因为当你判定一个数据未被处理,却在执行锁定后(锁定成功可能需要一段时间,有排队等待过程),可能当初认定未被处理的数据却已经被处理了。
这个代码有一个性能问题,就是多次重复锁定,没做到对一次查找只锁定一次,使得锁定处理结果:要么找到未被处理数据,要么数据处理完成。
下面的代码则解决了这个问题(增加定义了一个类成员变量,标识当前正在处理的记录ID)
int current = 0; private void Run() { while (true) { string email = null; lock (m_DataTable) { for (int i = current + 1; i < m_DataTable.Rows.Count; i++) { if ((int)m_DataTable.Rows[i]["Type"] != 0) { continue; } m_DataTable.Rows[i]["Type"] = 1; current = i; email = m_DataTable.Rows[i]["Email"].ToString(); } } if (email == null) { //结束 break; } //执行邮件发送等耗时操作 } }
for循环的性能要比foreach好,但foreach比for安全(下标跨界、集合队列改变,如增、删、排序等)。
@lanmiao: 用以下方案,性能、安全与可维护性会更好,因为不需要循环,把临界资源交给统一的一个仲裁机构来处理。
int current = 0; private DataRow GetDataRow() { lock (m_DataTable) { if (current < this.m_DataTable.Rows.Count) { return this.m_DataTable.Rows[current++]; } return null; } } private void Run() { while (true) { string email = null; DataRow dataRow = this.GetDataRow(); if (dataRow == null) { //结束 break; } dataRow["Type"] = 1; email = dataRow["Email"].ToString(); //执行邮件发送等耗时操作 } }
@笨笨蜗牛: 这方法确实不错,还有问问题能帮忙看看吗
private void Run() { while (true) { string email = null; DataRow dataRow = this.GetDataRow(); if (dataRow == null) { //结束 break; } dataRow["Type"] = 1; email = dataRow["Email"].ToString(); //执行邮件发送等耗时操作 这个地方想让15条线程都暂停,比如执行拨号换IP操作
} }
@lanmiao: 让线程都暂停,应该是让所有的线程都等待啊,锁住就行了吧
private void button2_Click(object sender, EventArgs e) { listBox1.Items.Clear(); int rowCount = m_DataTable.Rows.Count; int chunk = rowCount / 15; for (int i = 0; i < 15; i++) { int start = i * chunk; int end = (i == 14) ? rowCount - 1 : start + chunk; Thread thread = new Thread(new ThreadStart( () => Run(start, end, m_DataTable))); thread.Start(); } } void Run(int start, int end, DataTable table) { for (int i = start; i <= end; i++) { var dataRow = table.Rows[i]; dataRow["Type"] = 1; string email = dataRow["Email"].ToString(); //...... } }
先分区,再计算。可以避免每个线程对所有行的遍历,也消除了锁。
using System; using System.Diagnostics; using System.Threading; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { Thread[] threads = new Thread[2]; for (int i = 0; i < threads.Length; i++) { threads[i] = new Thread(new ParameterizedThreadStart(Run)); threads[i].Start(i); } } private static string uniqueID = "unique name"; private static void Run(object state) { while (true) { bool createdNew; using (System.Threading.Mutex mutex = new System.Threading.Mutex(true, uniqueID, out createdNew)) { if (!createdNew) { //如果互斥对象被别的线程构建,等待互斥对象被释放 Console.WriteLine("Thread {0} Entry Wating...", state); Debug.WriteLine("Thread {0} Entry Wating...", state); mutex.WaitOne(); Console.WriteLine("Thread {0} Exit Wating...", state); Debug.WriteLine("Thread {0} Exit Wating...", state); } } int working = new Random().Next(1000, 3000); Console.WriteLine("Thread {0} Do Something for {1}...", state, working); Debug.WriteLine("Thread {0} Do Something for {1}...", state, working); Thread.Sleep(working); Console.WriteLine("Thread {0} Do Something ok for {1}...", state, working); Debug.WriteLine("Thread {0} Do Something ok for {1}...", state, working); bool critical = new Random().Next(1000, 3000) % 10 == 0; if (critical) { Console.WriteLine("Thread {0} Entry critical...", state); while (true) { using (System.Threading.Mutex mutex = new System.Threading.Mutex(true, uniqueID, out createdNew)) { if (!createdNew) { //如果互斥对象被别的线程构建,等待互斥对象被释放 Console.WriteLine("Thread {0} Entry Wating for critical...", state); Debug.WriteLine("Thread {0} Entry Wating for critical...", state); mutex.WaitOne(); Console.WriteLine("Thread {0} Exit Wating for critical...", state); Debug.WriteLine("Thread {0} Exit Wating for critical...", state); } else { working = new Random().Next(1000, 3000); Console.WriteLine("Thread {0} Do critical Something for {1}...", state, working); Debug.WriteLine("Thread {0} Do critical Something for {1}...", state, working); Thread.Sleep(working); Console.WriteLine("Thread {0} Do critical Something ok for {1}...", state, working); Debug.WriteLine("Thread {0} Do critical Something ok for {1}...", state, working); mutex.ReleaseMutex(); break; } } } } } } } }
以上代码在前期运行可以,后期就出现错误,还没找到原因。有空再研究下。
并行
Parallel.Invoke(
()=>Run(),
()=>Run(),
...
);