关于多线程操作,无法同步执行的疑惑
环境:VS2008+C#+WinForm程序
界面有一保存按钮,执行过程很费时,希望能给用户一些友好显示,自然想到要用进度条;
希望在执行主方法时,进度条也同时显示,并逐步增长的过程。现在主方法与进度条方法都已经写好,单独运行可以成功。但希望达到的效果始终无法实现。
1、进度条与主方法,都有可能涉及到对界面控件的操作,为防止出现<线程间操作无效: 从不是创建控件“”的线程访问它。>这样的错误,进度条主要代码采用了委托:
/// <summary>
/// 进度条委托方法
/// </summary>
/// <param name="ipos"></param>
private void SetProgressBarDelegate(int ipos)
{
if (this.InvokeRequired)
{
this.Invoke(new DelegateIntoInt(this.SetProgressBarDelegate), new object[] { ipos });
}
else
{
base.toolStripProgressBar1.Value = Convert.ToInt32(ipos);
}
}
此时,主方法没有采用进度条这种this.Invoke方式,如果不包含界面控件操作,运行正常。如果加上界面控件操作,就会报错<线程间操作无效: 从不是创建控件“”的线程访问它。>,然后如法炮制,将主方法也改成下面这种:
/// <summary>
/// 执行主方法
/// </summary>
private void DoMainMethodDelegate()
{
if (this.InvokeRequired)
{
this.Invoke(new DelegateIntoNull(this.myMainMethod), new object[] { });
}
}
在保存按钮的主要代码如下:
this.proBarThread = new Thread(new ThreadStart(this.SetToolStripProgressBar));
this.proBarThread.Start();
//执行主线程方法
this.mainThread = new Thread(new ThreadStart(this.DoMainMethodDelegate));
this.mainThread.IsBackground = true;//后台线程
this.mainThread.Start();
结果运行程序,虽不报错了,但两个线程效果并未同时运行,而是将主方法执行完了,才执行的进度条。
本人愚钝,实在不知道问题出在哪里,请大家帮忙指导一下,不甚感激!
[解决办法]
用System.ComponentModel.BackgroundWorker不行吗?他有ReportProgress方法和ProgressChanged事件
[解决办法]
别把那个弱智的控件拿来谈。
楼主犯了2个错误:
第一是回调不能用this.Invoke,而必须用this.BeginInvoke,否则就容易死锁线程,除非某个地方必须卡住,待主线程的执行完毕后才继续执行,那时才需要用this.Invoke,在异步执行的线程中,this.Invoke的调用将等待主线程执行后,才会执行后面的代码,而this.BeginInvoke只是告诉主线程你要执行这部分代码,然后异步线程立刻执行后面的代码去了。
第二是主方法中全部回调就变成了同步执行了,压根没用到异步执行。你需要改写myMainMethod函数,将里面涉及到界面操作的部分单独封装,调用this.BeginInvoke来执行。
[解决办法]
后台处理任务 前台更新界面 没必要使用两个线程
void btn1_Click()
{
Thread th = new Thread((ThreadStart)delegate()
{
//处理任务1
this.Invoke((Action)delegate() //建议这儿使用 this.Invoke 不要使用 BeginInvoke 不然界面更新和实际工作进度可能不同步
{
this.ProgressBar.Value = 10;
});
//处理任务2
this.Invoke((Action)delegate()
{
this.ProgressBar.Value = 20;
});
//处理任务3
this.Invoke((Action)delegate()
{
this.ProgressBar.Value = 30;
});
// ...
});
th.Start();
}
Backgroundworker 不是控件 如果使用它 意思一样 把你逻辑代码 写进DoWork事件处理程序 根本不需要ReportProgress
back.DoWork += DoWorkCallBack;
void DoWorkCallBack(...)
{
//处理任务1
this.Invoke((Action)delegate() //建议这儿使用 this.Invoke 不要使用 BeginInvoke 不然界面更新和实际工作进度可能不同步
{
this.ProgressBar.Value = 10;
});
//处理任务2
this.Invoke((Action)delegate()
{
this.ProgressBar.Value = 20;
});
//处理任务3
this.Invoke((Action)delegate()
{
this.ProgressBar.Value = 30;
});
// ...
}
back.RunWorkAsync();
[解决办法]
所有的控件均由 UI线程负责(也就是main进入的线程) 不需要你另外开辟线程来操作它
[解决办法]
参考这个文章的解释和实现:http://www.cnblogs.com/zhili/archive/2013/05/11/EAP.html
[解决办法]
SetToolStripProgressBar不知道你这里面做了什么?
感觉你这个 没必要用两个线程
直接在DoMainMethodDelegate 里面按进度调用?SetProgressBarDelegate就行了啊。
[解决办法]
界面控制的地方多的话,你也只能见到一处封装一处了,要提高点编程效率的话,可以使用匿名方法和匿名委托,直接在函数中任意地方嵌入。至于你所谓的外部控制是多余的,既然是多线程执行,就不能考虑外部的控制加入,所有需要执行的UI操作要通过委托方式传递给你的myMainMethod函数,执行中适当的时候用this.BeginInvoke调用,当然,你不需要判断InvokeRequired属性,那个完全多余,即使已经处于UI线程,你再使用this.BeginInvoke调用你之前封装好的委托,也没任何错误。
最后要提醒下,这里所谓的this是当前类,如果是WinForm下面使用,自然是没错误,但是如果你在自定义类里面封装方法时使用,就肯定会出错了,所以可以考虑上下文同步类来处理UI同步处理的委托。详情参考线程之间的通讯---SynchronizationContext,这个类是处理UI同步操作的最佳方案,但是却很少有人知道。