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

clear_zero 2011-10-22 09:50:13
我用的是.net 2010的C#,winform.

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

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

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

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

谢谢
...全文
330 22 打赏 收藏 转发到动态 举报
写回复
用AI写文章
22 条回复
切换为时间正序
请发表友善的回复…
发表回复
窗户纸 2011-10-24
  • 打赏
  • 举报
回复
另外,方法一也可以更新进度条。
窗户纸 2011-10-24
  • 打赏
  • 举报
回复
[Quote=引用 14 楼 clear_zero 的回复:]
对不起,上面的判断||应该改成&&
不过方法1还是没有反应

谢谢
[/Quote]
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().
gxmark 2011-10-24
  • 打赏
  • 举报
回复
建议通过静态变量来标记完成的线程数,同时利用一个处理的标记来标记是否已经对线程的结果进行了处理,并通过异步调用的方式使用多线程处理。具体代码如下:
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个线程都处理完毕了。
yojinlin 2011-10-24
  • 打赏
  • 举报
回复
路過學習了。
clear_zero 2011-10-24
  • 打赏
  • 举报
回复
[Quote=引用 20 楼 etudiant6666 的回复:]

给你揉揉头,挤挤太阳穴,我刚接触时也有时范迷瞪,硬着头皮上着做几个案例就通了。
不过有C++底层编程的底子好些(那里有个互斥锁的概念),C#的线程需要占用线程栈,如果做服务器应用就得注意内存的使用,如果是界面应用就无所谓了。
[/Quote]
我理解这个互斥锁的概念。用你的方法2也实现了。只是这方法一并没有实现。我回头需要重新实践一下threadpool的东西.好像这个invoke我并没有搞明白
...笨死算了
窗户纸 2011-10-24
  • 打赏
  • 举报
回复
另外,异步编程的调试麻烦些,有时捕获不到中断,有时两个中断一起走很乱,这是最好把过程变量或异常抛出到调试文件或系统日志中。
窗户纸 2011-10-24
  • 打赏
  • 举报
回复
给你揉揉头,挤挤太阳穴,我刚接触时也有时范迷瞪,硬着头皮上着做几个案例就通了。
不过有C++底层编程的底子好些(那里有个互斥锁的概念),C#的线程需要占用线程栈,如果做服务器应用就得注意内存的使用,如果是界面应用就无所谓了。
clear_zero 2011-10-24
  • 打赏
  • 举报
回复
[Quote=引用 17 楼 etudiant6666 的回复:]

引用 14 楼 clear_zero 的回复:
对不起,上面的判断||应该改成&amp;&amp;
不过方法1还是没有反应

谢谢

1. 多线程操作需要考虑很多线程间同步的问题,需要考虑可能多个线程同时访问某段代码/某个变量带来的冲突问题,会频繁的使用lock命令,但lock不能锁int,bool等栈内的变量。
比如,如果我们担心两个采集线程同时执行造成的混乱,可……
[/Quote]
调了,没有进入任何一个线程的
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() ;
}

}
我回头再弄弄,搞不定阿...头疼中
clear_zero 2011-10-24
  • 打赏
  • 举报
回复
对不起,上面的判断||应该改成&&
不过方法1还是没有反应

谢谢
clear_zero 2011-10-24
  • 打赏
  • 举报
回复
[Quote=引用 12 楼 etudiant6666 的回复:]

另外还可以辅助的增加一个进度条,每完成一项工作,向进度条发送一个信号,用户还可以选择在未完成全部工作时取消作业,随时退出,
[/Quote]
方法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() ;
}

}


我哪里写错了么?求指导
谢谢
窗户纸 2011-10-23
  • 打赏
  • 举报
回复
另外还可以辅助的增加一个进度条,每完成一项工作,向进度条发送一个信号,用户还可以选择在未完成全部工作时取消作业,随时退出,
liuyilin888 2011-10-23
  • 打赏
  • 举报
回复
Thread.Join();
窗户纸 2011-10-23
  • 打赏
  • 举报
回复
[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的线程栈内存空间,放到界面则用户只能等待。而使用异步线程结束后回调处理,内存空间比较省。用户也可以进行其他操作(当然代价是速度比阻塞有极小的慢,慢的可以忽略)。
clear_zero 2011-10-23
  • 打赏
  • 举报
回复
我改了一下就可以了

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

不过这样是不是不好呢?还是这样是正常的方法?求解惑,谢谢
clear_zero 2011-10-23
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 etudiant6666 的回复:]

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

#region 异步回调
private void button5_Click(object sender, EventArgs e)
{
……
[/Quote]
我用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"); //好像进了死循环,一直看不到这里。
}

给点提示吧,再次感谢
clear_zero 2011-10-22
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 caozhy 的回复:]

是不是你调用了公寓多线程的ActiveX COM DLL?
在 void Main() 前加上[MTAThread]看看。
[/Quote]
你说得这个方法管用
窗户纸 2011-10-22
  • 打赏
  • 举报
回复
如果使用异步委托的话,有个例程给你看看,但理解起来很麻烦,你最好直接使用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
机器人 2011-10-22
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 caozhy 的回复:]

是不是你调用了公寓多线程的ActiveX COM DLL?
在 void Main() 前加上[MTAThread]看看。
[/Quote]

靠谱
窗户纸 2011-10-22
  • 打赏
  • 举报
回复
三个采集线程结束先后顺序是不一定的,使用主线程上waitone()之类的恐怕有些麻烦,倒不如在窗体类设三个判断变量,每个线程上的启动是由委托启动(或者直接使用BackGroundWorker),执行完就回调,回调函数设置对应线程结束的判断变量,并确定所有判断变量都设置完成了,即可发起后续作业,这样会好些。

delegate_xxA collectA=....;
delegate_xxB collectB=...;
delegate_xxC collectC=...;

collectA.BeginInvoke(。。。);
collectB.BeginInvoke(。。。);
collectC.BeginInvoke(。。。);

CalvinWang 2011-10-22
  • 打赏
  • 举报
回复
Thread.Join();
加载更多回复(2)

110,532

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧