c#+Parallel.ForEach的卡死现象

骑行大地 2014-04-22 06:03:33

private void button1_Click(object sender, EventArgs e)
{
string[] strs = new string[1000];
progressBar1.Value = 0;
progressBar1.Maximum = 1000;
for (int i = 0; i < 1000; i++)
strs[i] = i.ToString();

textBox1.Text = "";
Parallel.ForEach(strs, num =>
{
this.Invoke(new Action(() =>
{
textBox1.Text += num + ";";
textBox1.Refresh();

progressBar1.Value++;
progressBar1.Refresh();

label1.Text = progressBar1.Value + "/" + progressBar1.Maximum;
label1.Refresh();
}));
});
MessageBox.Show("successful");
}

代码如上,相信大家都能看的明白。代码的大意就是每向textbox1中增加一个字符串,进度条就+1,label1标签也显示+1。遇到的问题是,点击按钮后,进度运行到0-1000的某个数(比如99等)之后界面卡死,程序也不往下执行。不过有时又能正常执行,并顺利弹出“successful”对话框。请问这是为什么?
以上只是我要做的东西的缩略版,在我的程序中涉及到I/0写文件,每次都是运行到1/3处直接卡死,请教各位牛人给指点迷津啊,折腾了两三天了,没搞出来!
...全文
1617 7 打赏 收藏 转发到动态 举报
写回复
用AI写文章
7 条回复
切换为时间正序
请发表友善的回复…
发表回复
lookxzc 2016-11-01
  • 打赏
  • 举报
回复
4楼解释的不错 foreach并行生成多个线程执行方法体 每个线程又要调用主线程的invoke方法而依次阻塞等待 同时主线程也因为foreach方法没返回而阻塞 这样就造成了死锁

但我的疑问是4楼修改之后的代码 foreach方法体内依然要调用invoke方法 这样改了虽然把并行部分移出invoke方法 但是invoke依然会因为foreach没返回而死锁啊 希望解答解答
iyomumx 2014-04-23
  • 打赏
  • 举报
回复
先明确以下几点: 1.Parallel.ForEach会阻塞直到所有任务完成或任务取消,但你采用的重载不支持取消,故它会阻塞直到任务完成。 2.你的代码显示这个ForEach运行在窗体线程,即任务完成之前窗体线程将处于阻塞状态。 3.this.Invoke会请求窗体线程执行委托,并在委托完成前阻塞。如果调用Invoke的线程即为窗体线程,则直接在当前线程执行委托并返回。 综上,你的代码可能产生以下状况: 1.事件触发,事件处理程序开始 2.ForEach使用默认的任务调度器首先在当前线程(窗体线程)安排任务,并根据负载与等待情况安排其他工作线程参与执行任务。 3a.如果单个任务执行非常快以致于创建线程的开销远大于在当前线程执行剩余任务的开销,那么任务调度器不会创建其它工作线程,所有的Invoke都在当前线程得以执行,ForEach返回。 3b.如果创建线程可能加快任务处理速度,任务调度器会创建工作线程,并在工作线程上安排任务。 4b.在工作线程上执行的Invoke全部被阻塞,等待窗体线程进行处理。 5b.窗体线程执行所有可执行的任务后,等待ForEach返回,但由于其他线程在等待窗体线程处理Invoke,任务不能完成,ForEach无法返回,造成死锁。 在调试程序时,如果出现死锁的情况,可以立刻中断,并打开线程窗口(调试->窗口->线程)查看各个线程在何处发生阻塞 针对你的情况,我建议你在其他线程进行数据准备和并行循环:

private void button1_Click(object sender, EventArgs e)
{
    //界面调整,比如 button1.Enabled = false; 以防止重入
    //利用线程池安排一个线程执行并行循环
    System.Threading.ThreadPool.QueueUserWorkItem(w =>
    {
        //非并行部分,比如string[] strs = System.Linq.Enumerable.Range(0, 1000).ToArray();
        Parallel.ForEach(strs, num =>
                {
                    //并行部分,比如文件IO
                    this.Invoke(new Action(() =>
                    {
                        //界面更新
                    })); 
                });
        //并行处理结束,使用Invoke调用MessageBox.Show以显示一个模态窗口
        //相应的界面更改也写进Invoke调用里,比如 button1.Enabled = true
        this.Invoke(new MethodInvoker(() => MessageBox.Show("successful")));
    }, null);
}
lele_nancy 2014-04-23
  • 打赏
  • 举报
回复
没有问题啊,你的程序能跑啊
骑行大地 2014-04-23
  • 打赏
  • 举报
回复
引用 1 楼 caozhy 的回复:
label1.Refresh(); Application.DoEvents();
谢谢你的回复。不过,我加了Application.DoEvents();这一句代码之后,运行到九百多的时候,有时还是会卡死!请问这是为什么?
骑行大地 2014-04-23
  • 打赏
  • 举报
回复
谢谢#4的回复,你的解答正是我想要的。按你说的修改代码之后,正常运行。
threenewbee 2014-04-22
  • 打赏
  • 举报
回复
label1.Refresh(); Application.DoEvents();

110,534

社区成员

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

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

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