当前位置:编程学习 > C#/ASP.NET >>

c#多线程问题(.net 2010)

我用的是.net 2010的C#,winform.

目标是在窗体上按下按钮,3条线程同时对三个文件进行信息采集。当3个进程全部结束后再进行信息整合。

我尝试了Threadool但是在waithandle.waitall的时候得到了"wait for multiple handles on a sta thread is not supported"的错误。然后才知道从窗体开始执行的thread都是STA。 

如果是这样的话,如何才能得到我想要的结果呢?

我刚开始学习,不是很理解,还望大家说得详细点

谢谢 --------------------编程问答-------------------- 是不是你调用了公寓多线程的ActiveX COM DLL?
在 void Main() 前加上[MTAThread]看看。 --------------------编程问答-------------------- use Thread.Joing() to wait other thread finish their jobs. --------------------编程问答-------------------- Thread.Join(); --------------------编程问答-------------------- 三个采集线程结束先后顺序是不一定的,使用主线程上waitone()之类的恐怕有些麻烦,倒不如在窗体类设三个判断变量,每个线程上的启动是由委托启动(或者直接使用BackGroundWorker),执行完就回调,回调函数设置对应线程结束的判断变量,并确定所有判断变量都设置完成了,即可发起后续作业,这样会好些。
  
  delegate_xxA collectA=....;
  delegate_xxB collectB=...;
  delegate_xxC collectC=...;

  collectA.BeginInvoke(。。。);
  collectB.BeginInvoke(。。。);
  collectC.BeginInvoke(。。。);
  
  --------------------编程问答--------------------
引用 1 楼 caozhy 的回复:
是不是你调用了公寓多线程的ActiveX COM DLL?
在 void Main() 前加上[MTAThread]看看。


靠谱 --------------------编程问答-------------------- 如果使用异步委托的话,有个例程给你看看,但理解起来很麻烦,你最好直接使用BackgroundWorker这个控件,(在控件工具箱里面有),设置看看说明很快就会了。

        #region 异步回调
        private void button5_Click(object sender, EventArgs e)
        {
            EventHandler p = new EventHandler(button2);
            p.BeginInvoke(sender, e, new AsyncCallback(CallBackMth), null);
        }

        void CallBackMth(IAsyncResult target)
        {
            //后续操作....
        }
        #endregion
--------------------编程问答--------------------
引用 1 楼 caozhy 的回复:
是不是你调用了公寓多线程的ActiveX COM DLL?
在 void Main() 前加上[MTAThread]看看。

你说得这个方法管用 --------------------编程问答--------------------
引用 6 楼 etudiant6666 的回复:
如果使用异步委托的话,有个例程给你看看,但理解起来很麻烦,你最好直接使用BackgroundWorker这个控件,(在控件工具箱里面有),设置看看说明很快就会了。
C# code

        #region 异步回调
        private void button5_Click(object sender, EventArgs e)
        {
            ……

我用backgroundworker的话怎么做能够等到三个进程都结束了才开始信息的合成,我这样写的又不行

        private void button3_Click(object sender, EventArgs e)
        {
            bgw1.RunWorkerAsync();
            Thread.Sleep(10);
            bgw2.RunWorkerAsync();
            Thread.Sleep(10);
            bgw3.RunWorkerAsync();
            Thread.Sleep(10);
            while (bgw1.IsBusy && bgw2.IsBusy && bgw3.IsBusy)
            {
                // Console.WriteLine ("hello");
            }
            MessageBox.Show("all finished"); //好像进了死循环,一直看不到这里。
        }

给点提示吧,再次感谢 --------------------编程问答-------------------- 我改了一下就可以了

            while (bgw1.IsBusy && bgw2.IsBusy && bgw3.IsBusy)
            {
                // Console.WriteLine ("hello");
                application.doevents();
            }
            MessageBox.Show("all finished"); 

不过这样是不是不好呢?还是这样是正常的方法?求解惑,谢谢 --------------------编程问答-------------------- [Quote=引用 8 楼 clear_zero 的回复:]
我用backgroundworker的话怎么做能够等到三个进程都结束了才开始信息的合成,我这样写的又不行

        private void button3_Click(object sender, EventArgs e)
        {
            bgw1.RunWorkerAsync();
            Thread.Sleep(10);
            bgw2.RunWorkerAsync();
            Thread.Sleep(10);
            bgw3.RunWorkerAsync();
            Thread.Sleep(10);
            while (bgw1.IsBusy && bgw2.IsBusy && bgw3.IsBusy)
            {
                // Console.WriteLine ("hello");
            }
            MessageBox.Show("all finished"); //好像进了死循环,一直看不到这里。
        }

给点提示吧,再次感谢 
Quote]
这样编码效率并不高,并没有脱离同步编程的思考模式,异步编程需要改变一下思路:即线程执行完后,系统会自动调用回调函数,后续的执行不是在主线程等待,而是在回调函数中处理。
即对backgroundWorker注册RunWorkerCompleted事件,在事件中判断是否所有收集工作都完成了。


#if 方式1
ManuResetEvent _CEvent= new ...;
#endif
  bool _IsBgw1Complete;
  bool _IsBgw2Complete;
  bool _IsBgw3Complete;

 private void button3_Click(object sender, EventArgs e)
 {
#if 方式1
   CEvent.Reset();
#endif
   _IsBgw1Complete=false;
   _IsBgw2Complete=false;
   _IsBgw3Complete=false;
   bgw1.RunWorkerAsync();
   bgw2.RunWorkerAsync();
   bgw3.RunWorkerAsync();
#if 方式1
 //方式1:系统需要等待所有采集线程完成,则可设置一个ManuResetEvent,在
  if(  _CEvent.WaitOne(可以设置等待的超时))
  {
    DoAfterCollect()
   }
  else
  {
    //超时失败的处理
   }
#else
  //方式2: 不阻塞,直接关闭,界面可以直接处理其他事务,而在所有采集工作完成后,自动弹出后续操作需要的窗体。因此直接结束即可

#endif
  
 }


private void bgw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
  _IsBgw1Complete=true;
  if (_IsBgw1Complete && _IsBgw2Complete&& _IsBgw3Complete)
   {
#if 方式1
    _CEvent.Set();
#else
  //方式2,
 DoAfterCollect();
#endif
   }
}

private void DoAfterCollect()
{
    // Console.WriteLine ("hello");
}



使用主线程waitone()一类的方法,主线程一直被阻塞,会占用2MB的线程栈内存空间,放到界面则用户只能等待。而使用异步线程结束后回调处理,内存空间比较省。用户也可以进行其他操作(当然代价是速度比阻塞有极小的慢,慢的可以忽略)。
--------------------编程问答-------------------- Thread.Join(); --------------------编程问答-------------------- 另外还可以辅助的增加一个进度条,每完成一项工作,向进度条发送一个信号,用户还可以选择在未完成全部工作时取消作业,随时退出, --------------------编程问答--------------------
引用 12 楼 etudiant6666 的回复:
另外还可以辅助的增加一个进度条,每完成一项工作,向进度条发送一个信号,用户还可以选择在未完成全部工作时取消作业,随时退出,

方法2成功了,但是1不成功不知道为什么。
我已经添加了进度条,方法2的时候更新进度条。处理DoAfterCollect都没有问题。我想用方法1是因为方法1还是归总到主进程来。而方法2会在三个地方触发虽然可以通过变量控制但是感觉不是很好。方法1也不更新进度条

//声明的部分
        private ManualResetEvent m_ResetEvent = new ManualResetEvent(false);
        private bool m_bgw1Finish=false;
        private bool m_bgw2Finish=false ;
        private bool m_bgw3Finish=false ;
....
....
//开始进程的代码
        private void button3_Click(object sender, EventArgs e)
        {
            m_ResetEvent.Reset();
            m_bgw1Finish = false;
            m_bgw2Finish = false;
            m_bgw3Finish = false;
            bgw1.RunWorkerAsync();
            Thread.Sleep(10);
            bgw2.RunWorkerAsync();
            Thread.Sleep(10);
            bgw3.RunWorkerAsync();
            Thread.Sleep(10);
            //MessageBox.Show("all finished");
            if (m_ResetEvent.WaitOne( ))
            {
                MessageBox.Show("all finished");
            }
        }
//我在每个进程结束的事件里面都写了相应的代码
        private void bgw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            m_bgw1Finish = true;
            if (m_bgw1Finish || m_bgw2Finish || m_bgw3Finish)
            {
              //  MessageBox.Show("all finished");
                m_ResetEvent.Set() ;
            }
           
        }


我哪里写错了么?求指导
谢谢
--------------------编程问答-------------------- 对不起,上面的判断||应该改成&&
不过方法1还是没有反应

谢谢 --------------------编程问答-------------------- 路過學習了。 --------------------编程问答-------------------- 建议通过静态变量来标记完成的线程数,同时利用一个处理的标记来标记是否已经对线程的结果进行了处理,并通过异步调用的方式使用多线程处理。具体代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Runtime.Remoting.Messaging;


namespace ConsoleApplication1
{
    class Program
    {
        public static int ThreadCount = 0;//用于标记已经结束处理的线程数
        public static int FinishedProcess = 0;//用于标记是否进行了线程处理整合的标记,0代表未处理,1代表已经处理了
        static void Main(string[] args)
        {            
            //定义三个委托并指向不同的方法
            Func<int, string> workerMethod1 = Method1;
            Func<int, string> workerMethod2 = Method2;
            Func<int, string> workerMethod3 = Method3;

            //进行委托的异步调用,参数1只是作为一个指示,没有什么实际意义,回调函数都定义为CallBack
            IAsyncResult asyncResult1 = workerMethod1.BeginInvoke(1, new AsyncCallback(CallBack), null);
            IAsyncResult asyncResult2 = workerMethod2.BeginInvoke(1, new AsyncCallback(CallBack), null);
            IAsyncResult asyncResult3 = workerMethod3.BeginInvoke(1, new AsyncCallback(CallBack), null);

            //防止程序运行则终止
            Console.ReadKey();
        }

        public static void CallBack(IAsyncResult ar)
        {
            Console.WriteLine("当前线程为:"+Thread.CurrentThread.ManagedThreadId + "ThreadCount值为:" + ThreadCount);
            Func<int, string> proc=((AsyncResult)ar).AsyncDelegate as Func<int, string>;
            string c = proc.EndInvoke(ar);
            if (c == "3"&&FinishedProcess==0)
            {
                FinishedProcess = 1;
                Console.WriteLine("所有线程已经处理完毕,请在此处进行最后的整合");
            }            
        }
        
        public static string Method1(int i)
        {
            ThreadCount++;
            Console.WriteLine("方法:Method1;当前线程为:" + Thread.CurrentThread.ManagedThreadId+";ThreadCount值为:"+ThreadCount);
            return ThreadCount.ToString();

        }
        public static string Method2(int i)
        {
            ThreadCount++;
            Console.WriteLine("方法:Method2;当前线程为:" + Thread.CurrentThread.ManagedThreadId + ";ThreadCount值为:" + ThreadCount);
            return ThreadCount.ToString();

        }
        public static string Method3(int i)
        {
            ThreadCount++;
            Console.WriteLine("方法:Method3;当前线程为:" + Thread.CurrentThread.ManagedThreadId + ";ThreadCount值为:" + ThreadCount);
            return ThreadCount.ToString();
        }        
    }
}

上面代码中在每个线程处理完毕的时候都对TreadCount这个变量加1,同时在回调函数CallBack中判断当前完成的线程数是否为3,若是三则进行必要的处理,同时对处理标记FinishedProcess置为1,这样保证只对结果进行一次处理。
运行结果不确定,在多核机器中,基本上是同步完成的。
在实际操作中应该把ThreadCount++放在每个处理逻辑的最后,保证是处理线程的最后一个工作。这样就能保证当ThreadCount为3的时候标识3个线程都处理完毕了。 --------------------编程问答--------------------
引用 14 楼 clear_zero 的回复:
对不起,上面的判断||应该改成&&
不过方法1还是没有反应

谢谢

1. 多线程操作需要考虑很多线程间同步的问题,需要考虑可能多个线程同时访问某段代码/某个变量带来的冲突问题,会频繁的使用lock命令,但lock不能锁int,bool等栈内的变量。
比如,如果我们担心两个采集线程同时执行造成的混乱,可以:
object _ForLockObj=new object();

....
lock(_ForLockObj)
{
 if (m_bgw1Finish || m_bgw2Finish || m_bgw3Finish)
            {
              //  MessageBox.Show("all finished");
                m_ResetEvent.Set() ;
            }
}
2. 对于方法1没有调通的问题,其实比较好调,
  1)对 m_ResetEvent.waitone()设置超时,看看是否为超时未等到,如果是,则为多线程内逻辑问题。
  2)如果未等到,还可以观察各个判断变量那个发生变化了,如果都没变化,看看 m_ResetEvent是否reset(). --------------------编程问答-------------------- 另外,方法一也可以更新进度条。 --------------------编程问答--------------------
引用 17 楼 etudiant6666 的回复:
引用 14 楼 clear_zero 的回复:
对不起,上面的判断||应该改成&amp;&amp;
不过方法1还是没有反应

谢谢

1. 多线程操作需要考虑很多线程间同步的问题,需要考虑可能多个线程同时访问某段代码/某个变量带来的冲突问题,会频繁的使用lock命令,但lock不能锁int,bool等栈内的变量。
比如,如果我们担心两个采集线程同时执行造成的混乱,可……

调了,没有进入任何一个线程的
        private void bgw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            m_bgw1Finish = true;
            if (m_bgw1Finish && m_bgw2Finish && m_bgw3Finish)
            {
              //  MessageBox.Show("all finished");
                m_ResetEvent.Set() ;
            }
           
        }
我回头再弄弄,搞不定阿...头疼中 --------------------编程问答-------------------- 给你揉揉头,挤挤太阳穴,我刚接触时也有时范迷瞪,硬着头皮上着做几个案例就通了。
不过有C++底层编程的底子好些(那里有个互斥锁的概念),C#的线程需要占用线程栈,如果做服务器应用就得注意内存的使用,如果是界面应用就无所谓了。 --------------------编程问答-------------------- 另外,异步编程的调试麻烦些,有时捕获不到中断,有时两个中断一起走很乱,这是最好把过程变量或异常抛出到调试文件或系统日志中。 --------------------编程问答--------------------
引用 20 楼 etudiant6666 的回复:
给你揉揉头,挤挤太阳穴,我刚接触时也有时范迷瞪,硬着头皮上着做几个案例就通了。
不过有C++底层编程的底子好些(那里有个互斥锁的概念),C#的线程需要占用线程栈,如果做服务器应用就得注意内存的使用,如果是界面应用就无所谓了。

我理解这个互斥锁的概念。用你的方法2也实现了。只是这方法一并没有实现。我回头需要重新实践一下threadpool的东西.好像这个invoke我并没有搞明白
...笨死算了
补充:.NET技术 ,  C#
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,