none
多线程 返回值 和停止的问题 RRS feed

  • 问题

  • 需求如下:画面上 2个Button 一个是"Start" 另外一个是"Stop" 现在点击 "Strat"按钮 使用多线程查询指定网段内的信息,由于处理比较
    耗费时间 使用多线程处理,把查询到的 结果一个DataTable返回给 画面用DataGridView显示出来,由于使用了多线程因此每个子线程执行的时间消耗是不一样的,因此必须让主线程等待,所有的子线程执行结束后在把每个子线程收集到的结果合并到一个DataTable里面返回给画面。这个过程内如果 觉得查询时间长点击"Stop"按钮 停止掉当前动作。这就是我提这个问题的需求,解决线程同步和停止线程。
    以下是我的暂时代码
    public void Polling()
    {
      try
      {
        ...........
        Thread[] mythread = new Thread[Count];
          for (int i=0; i <Count;i ++)
          {
            mythread = new Thread(new ParameterizedThreadStart(gatherOnlineTime));                     
        ....................
            mythread.Start(obj);                    
          }
    for (int i = 0; i < dtIPSegment.Rows.Count; i++)
                    {
                        while (mythread.ThreadState == ThreadState.Running)
                        {
                            Thread.Sleep(2000);
                        }
                    }  
            }
      catch
      {
        ..............
      }

    现在我的这段代码可以实现 返回结果值 显示。但是这样程序运行得时候 会像死机一样,因此 界面上的 "Stop"按钮点击不了。
    我现在主要觉得问题在
     for (int i = 0; i < dtIPSegment.Rows.Count; i++)
                    {
                        while (mythread.ThreadState == ThreadState.Running)
                        {
                            Thread.Sleep(2000);
                        }
                    }
    在这里 但是 现在 我想让 主线程 等待 子线程 只找到了 这个方法。
    2009年11月19日 2:28

答案

  • 你好!这里你可以使用多个 backgroundworker 控件来代替你目前的方式。

    比如下面的示例。

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    using System.Threading;
    
    namespace WindowsFormsApplication9
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
                this.InitUI();
            }
    
            #region " InitUI "
    
            private System.Windows.Forms.DataGridView dataGridView1;
            private System.Windows.Forms.Panel panel1;
            private System.Windows.Forms.Panel panel2;
            private System.Windows.Forms.Button btnEnd;
            private System.Windows.Forms.Button btnStart;
            private BindingSource bs;
            private DataTable dt;
    
            private void InitUI()
            {
                this.dataGridView1 = new System.Windows.Forms.DataGridView();
                this.panel1 = new System.Windows.Forms.Panel();
                this.panel2 = new System.Windows.Forms.Panel();
                this.btnEnd = new System.Windows.Forms.Button();
                this.btnStart = new System.Windows.Forms.Button();
                
                this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
                this.dataGridView1.Dock = System.Windows.Forms.DockStyle.Fill;
                
                this.panel1.Controls.Add(this.dataGridView1);
                this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
                
                this.panel2.Controls.Add(this.btnEnd);
                this.panel2.Controls.Add(this.btnStart);
                this.panel2.Dock = System.Windows.Forms.DockStyle.Bottom;
                
                this.btnEnd.Location = new System.Drawing.Point(149, 7);
                this.btnEnd.Name = "btnEnd";
                this.btnEnd.Size = new System.Drawing.Size(75, 26);
                this.btnEnd.Text = "End";
                this.btnEnd.Click += new System.EventHandler(this.btnEnd_Click);
                
                this.btnStart.Location = new System.Drawing.Point(68, 7);
                this.btnStart.Name = "btnStart";
                this.btnStart.Size = new System.Drawing.Size(75, 26);
                this.btnStart.Text = "Start";
                this.btnStart.Click += new System.EventHandler(this.btnStart_Click);
                
                this.Controls.Add(this.panel2);
                this.Controls.Add(this.panel1);
                this.Name = "Form1";
                this.Text = "Form1";
    
                this.dt = new DataTable();
                this.dt.Columns.Add("Id");
    
                this.bs = new BindingSource(this.dt, null);
                this.dataGridView1.DataSource = this.bs;
    
            }
    
            #endregion
    
            private object lockObj = new object();
            private bool hasFinish = false;
    
            public bool HasFinish
            {
                get { return hasFinish; }
                set
                {
                    lock (lockObj)
                    {
                        hasFinish = value;
                    }
                }
            }
    
            private void btnStart_Click(object sender, EventArgs e)
            {
                if (this.HasFinish)
                    this.HasFinish = false;
                if (!this.HasFinish)
                    this.Run(5);
            }
    
            private void btnEnd_Click(object sender, EventArgs e)
            {
                if (!this.HasFinish)
                    this.HasFinish = true;
            }
    
            /// <summary>
            /// 启动多个线程获取数据
            /// </summary>
            /// <param name="fThreadCount">需要启动的线程数</param>
            private void Run(Int32 fThreadCount)
            {
                for (int i = 0; i < fThreadCount; i++)
                {
                    using (BackgroundWorker backgroundWorker = new BackgroundWorker())
                    {
                        DataTable dataFromServer = this.dt.Clone();
    
                        backgroundWorker.DoWork += new DoWorkEventHandler(delegate(object o, DoWorkEventArgs workerEventArgs)
                        {
                            while (dataFromServer.Rows.Count < 5)
                            {
                                dataFromServer.Rows.Add(new object[] { Guid.NewGuid().ToString() });
                                Thread.Sleep(100);
                            }
    
                            // 获取完成
                            // this.HasFinish = true;
                        });
    
                        backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(delegate(object o, RunWorkerCompletedEventArgs e)
                        {
                            if (dataFromServer.Rows.Count > 0)
                            {
                                // 获取部分数据后将这部分数据显示到界上
                                this.dt.Merge(dataFromServer);
                                this.dataGridView1.Sort(this.dataGridView1.Columns[0], ListSortDirection.Descending);
                                this.Text = String.Format("RowCount:{0}", this.dt.Rows.Count);
                            }
    
                            // 如果整个数据还未获取完成,继续开启一个线程来获取。
                            if (!this.HasFinish)
                                this.Run(1);
                        });
    
                        backgroundWorker.RunWorkerAsync();
                    }
                }
            }
        }
    }
    


    知识改变命运,奋斗成就人生!
    2009年11月19日 2:44
    版主
  • 你好!
         像这样的需求一般不用阻塞主线程,你阻塞了界面一定没有响应了!
         所以,你直接用另外一个线程查询数据,获得结果后,也用这个线程来更新DataGridView就可以了!
         你上面的做法和单线程效果一样了,没有体现出多线程的优势啊!
    周雪峰
    2009年11月19日 3:30
    版主

全部回复

  • 你好!这里你可以使用多个 backgroundworker 控件来代替你目前的方式。

    比如下面的示例。

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    using System.Threading;
    
    namespace WindowsFormsApplication9
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
                this.InitUI();
            }
    
            #region " InitUI "
    
            private System.Windows.Forms.DataGridView dataGridView1;
            private System.Windows.Forms.Panel panel1;
            private System.Windows.Forms.Panel panel2;
            private System.Windows.Forms.Button btnEnd;
            private System.Windows.Forms.Button btnStart;
            private BindingSource bs;
            private DataTable dt;
    
            private void InitUI()
            {
                this.dataGridView1 = new System.Windows.Forms.DataGridView();
                this.panel1 = new System.Windows.Forms.Panel();
                this.panel2 = new System.Windows.Forms.Panel();
                this.btnEnd = new System.Windows.Forms.Button();
                this.btnStart = new System.Windows.Forms.Button();
                
                this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
                this.dataGridView1.Dock = System.Windows.Forms.DockStyle.Fill;
                
                this.panel1.Controls.Add(this.dataGridView1);
                this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
                
                this.panel2.Controls.Add(this.btnEnd);
                this.panel2.Controls.Add(this.btnStart);
                this.panel2.Dock = System.Windows.Forms.DockStyle.Bottom;
                
                this.btnEnd.Location = new System.Drawing.Point(149, 7);
                this.btnEnd.Name = "btnEnd";
                this.btnEnd.Size = new System.Drawing.Size(75, 26);
                this.btnEnd.Text = "End";
                this.btnEnd.Click += new System.EventHandler(this.btnEnd_Click);
                
                this.btnStart.Location = new System.Drawing.Point(68, 7);
                this.btnStart.Name = "btnStart";
                this.btnStart.Size = new System.Drawing.Size(75, 26);
                this.btnStart.Text = "Start";
                this.btnStart.Click += new System.EventHandler(this.btnStart_Click);
                
                this.Controls.Add(this.panel2);
                this.Controls.Add(this.panel1);
                this.Name = "Form1";
                this.Text = "Form1";
    
                this.dt = new DataTable();
                this.dt.Columns.Add("Id");
    
                this.bs = new BindingSource(this.dt, null);
                this.dataGridView1.DataSource = this.bs;
    
            }
    
            #endregion
    
            private object lockObj = new object();
            private bool hasFinish = false;
    
            public bool HasFinish
            {
                get { return hasFinish; }
                set
                {
                    lock (lockObj)
                    {
                        hasFinish = value;
                    }
                }
            }
    
            private void btnStart_Click(object sender, EventArgs e)
            {
                if (this.HasFinish)
                    this.HasFinish = false;
                if (!this.HasFinish)
                    this.Run(5);
            }
    
            private void btnEnd_Click(object sender, EventArgs e)
            {
                if (!this.HasFinish)
                    this.HasFinish = true;
            }
    
            /// <summary>
            /// 启动多个线程获取数据
            /// </summary>
            /// <param name="fThreadCount">需要启动的线程数</param>
            private void Run(Int32 fThreadCount)
            {
                for (int i = 0; i < fThreadCount; i++)
                {
                    using (BackgroundWorker backgroundWorker = new BackgroundWorker())
                    {
                        DataTable dataFromServer = this.dt.Clone();
    
                        backgroundWorker.DoWork += new DoWorkEventHandler(delegate(object o, DoWorkEventArgs workerEventArgs)
                        {
                            while (dataFromServer.Rows.Count < 5)
                            {
                                dataFromServer.Rows.Add(new object[] { Guid.NewGuid().ToString() });
                                Thread.Sleep(100);
                            }
    
                            // 获取完成
                            // this.HasFinish = true;
                        });
    
                        backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(delegate(object o, RunWorkerCompletedEventArgs e)
                        {
                            if (dataFromServer.Rows.Count > 0)
                            {
                                // 获取部分数据后将这部分数据显示到界上
                                this.dt.Merge(dataFromServer);
                                this.dataGridView1.Sort(this.dataGridView1.Columns[0], ListSortDirection.Descending);
                                this.Text = String.Format("RowCount:{0}", this.dt.Rows.Count);
                            }
    
                            // 如果整个数据还未获取完成,继续开启一个线程来获取。
                            if (!this.HasFinish)
                                this.Run(1);
                        });
    
                        backgroundWorker.RunWorkerAsync();
                    }
                }
            }
        }
    }
    


    知识改变命运,奋斗成就人生!
    2009年11月19日 2:44
    版主
  • 你好!
         像这样的需求一般不用阻塞主线程,你阻塞了界面一定没有响应了!
         所以,你直接用另外一个线程查询数据,获得结果后,也用这个线程来更新DataGridView就可以了!
         你上面的做法和单线程效果一样了,没有体现出多线程的优势啊!
    周雪峰
    2009年11月19日 3:30
    版主
  • 你好,谢谢。
    有一个小问题问一下,因为 我之所以使用线程是因为 这个动作耗时,我想知道 用多个
    backgroundworker 它是同时执行的么?还有 可以让这个多个backgroundworker 对象
    控制在同一时刻结束么?
    谢谢你提供的 方案。
    2009年11月19日 3:43
  • 你好!

    我上面示例中的 HasFinish 属性,就是控制所有在运行中的 backGroupWorker 的。当 HasFinish 为 true,运行中的 backGroupWorker 都会停止。

    知识改变命运,奋斗成就人生!
    2009年11月19日 3:52
    版主
  • 感谢大家的帮助, 谢谢。
    2009年11月19日 3:54
  • To  X.X.Y  Thank you 有几个问题 不懂,询问一下。
    1, lockObj 这个变量 在程序里面有什么用,看了上面的代码感觉 只是 在Lock部分用了。
    2,代码 while (dataFromServer.Rows.Count < 5)
                            {
                                dataFromServer.Rows.Add(new object[] { Guid.NewGuid().ToString() });
                                Thread.Sleep(100);
                            }
    这个部分 按照我的理解 如果有这种 判断的话 画面上的鼠标会一直处于漏斗形状,不可以点击 任何按钮,而且这个时间的设置
    根据程序运行时间的不同 里面的数值 会变化的的吧,比如我的线程执行的时候 有的可能要20多秒,有的可能要1分钟,这个地方
    该怎么处理呢?您写的这个Demo 因为 仅仅是处理了一个字段的数据,所以可以设置成     Thread.Sleep(100); 但是 我在程序里面
    该怎么设置呢?根据我目前测试结果来看 如果写成 Thread.Sleep(Time); 这样形式的时候,程序的鼠标 没有办法点击该画面上的任何按钮直到这个程序执行结束。


    2009年11月23日 6:49
  • 1 lock 即锁解决多线程中资源共享访问
    2 其实上面的例子效果就是你需要的。

    首先要了解 BackgroundWorker.DoWork 事件中的代码块是运行在一个事件中的,Dowork 事件结果后会加调 RunWorkerCompleted 事件。

    using (BackgroundWorker backgroundWorker = new BackgroundWorker())
    {
        // 复制表结构
        DataTable dataFromServer = this.dt.Clone();

        backgroundWorker.DoWork += new DoWorkEventHandler(delegate(object o, DoWorkEventArgs workerEventArgs)
        {
            // 模拟从服务器获取数据
            // 你可以将这部分代码换成你获取数据的代码
            while (dataFromServer.Rows.Count < 5)
            {
                dataFromServer.Rows.Add(new object[] { Guid.NewGuid().ToString() });
                Thread.Sleep(100);
            }

            // 获取完成
            // this.HasFinish = true;
        });

        backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(delegate(object o, RunWorkerCompletedEventArgs e)
        {
            // 数据获取完成,将获取的数据合并到 dt 中
            if (dataFromServer.Rows.Count > 0)
            {
                // 获取部分数据后将这部分数据显示到界上
                this.dt.Merge(dataFromServer);
                this.dataGridView1.Sort(this.dataGridView1.Columns[0], ListSortDirection.Descending);
                this.Text = String.Format("RowCount:{0}", this.dt.Rows.Count);
            }

            // 如果整个数据还未获取完成,继续开启一个线程来获取。
            if (!this.HasFinish)
                this.Run(1);
        });

        backgroundWorker.RunWorkerAsync();

    知识改变命运,奋斗成就人生!
    2009年11月24日 1:25
    版主