首页 新闻 赞助 找找看

C#15条线程遍历同一个DataTable,在线等

0
悬赏园豆:200 [待解决问题]

多线程遍历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后,执行耗时操作
                }
            }
        }
lanmiao的主页 lanmiao | 初学一级 | 园豆:37
提问于:2012-06-04 10:28
< >
分享
所有回答(4)
0

我猜测,楼主大概是这种代码不会去写,那我说一下大概想路,首先需要一个函数去调度各线程所需要读取的数据。当没有数据可读取时,线程休眠。或者采取一个更为简单的策略:线程1读取datatable中第15*N+0行进行处理,线程2读取datatable中第15*N+1行,……没有数据就休眠。其他的,楼主的代码应该可以用。

sinhbv | 园豆:2579 (老鸟四级) | 2012-06-04 10:32

想过这个,感觉不妥,我不只是遍历,如果取得行数据进行一个耗时操作没成功,还要继续等下一轮来做

支持(1) 反对(0) lanmiao | 园豆:37 (初学一级) | 2012-06-04 10:58

@lanmiao: 你可以实现一个调度的函数,主要就是实现一个处理的队列,把需要处理的行号放入队列中去。在处理开始是,从队列中请求一个行号,然后去处理,如果出错,就将该行号插入到队列尾部去。当然了,这种方法的缺陷在于,队列中保存的是行号而不是数据,datatable不能做更新处理。最好的方法是,在队列中直接保存数据,也就是说读取数据的这个操作不放入到多线程中去做。这样的话,可随时更新数据,可随时处理。延伸一下就是,开启多少线程去处理,也可以放入调度中去做了。

支持(0) 反对(0) sinhbv | 园豆:2579 (老鸟四级) | 2012-06-04 11:06

@sinhbv: 能详细点么

支持(0) 反对(0) lanmiao | 园豆:37 (初学一级) | 2012-06-04 14:14

@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; }
    }

大概就是这个样子。

支持(0) 反对(0) sinhbv | 园豆:2579 (老鸟四级) | 2012-06-05 11:41
2

感觉你补充的代码没什么问题的,但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后,执行耗时操作
            }
        } 
无之无 | 园豆:5095 (大侠五级) | 2012-06-04 11:35

我按你这么做了,暂时没出现问题,能解释下么 或者会不会存在隐患(漏读、重读)

支持(0) 反对(0) lanmiao | 园豆:37 (初学一级) | 2012-06-04 13:54

@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安全(下标跨界、集合队列改变,如增、删、排序等)。

支持(0) 反对(0) 无之无 | 园豆:5095 (大侠五级) | 2012-06-04 15:16

@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();
                //执行邮件发送等耗时操作
            }
        }
支持(0) 反对(0) 无之无 | 园豆:5095 (大侠五级) | 2012-06-04 16:28

@笨笨蜗牛: 这方法确实不错,还有问问题能帮忙看看吗

 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操作
} }
支持(0) 反对(0) lanmiao | 园豆:37 (初学一级) | 2012-06-05 09:39

@lanmiao:   让线程都暂停,应该是让所有的线程都等待啊,锁住就行了吧

支持(0) 反对(0) havid | 园豆:70 (初学一级) | 2012-07-09 11:14
2
     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();
                //......
            }
        }

先分区,再计算。可以避免每个线程对所有行的遍历,也消除了锁。

Gavin2010 | 园豆:240 (菜鸟二级) | 2012-06-04 14:08
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;
                            }
                        }
                    }
                }
            }
        }
    }


}

以上代码在前期运行可以,后期就出现错误,还没找到原因。有空再研究下。

支持(0) 反对(0) 无之无 | 园豆:5095 (大侠五级) | 2012-06-05 14:16
0

并行
Parallel.Invoke(
()=>Run(),

()=>Run(),

...
);

dwwwing | 园豆:661 (小虾三级) | 2012-06-06 13:36
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册