c#使用后台线程调用的线程问题

我有线程,它处理一些分析工作。

private static void ThreadProc(object obj) { var grid = (DataGridView)obj; foreach (DataGridViewRow row in grid.Rows) { if (Parser.GetPreparationByClientNameForSynonims(row.Cells["Prep"].Value.ToString()) != null) UpdateGridSafe(grid,row.Index,1); Thread.Sleep(10); } } 

我想安全地更新我的gridView循环,所以我使用经典的方式:

  private delegate void UpdateGridDelegate(DataGridView grid, int rowIdx, int type); public static void UpdateGridSafe(DataGridView grid, int rowIdx, int type) { if (grid.InvokeRequired) { grid.Invoke(new UpdateGridDelegate(UpdateGridSafe), new object[] { grid, rowIdx, type }); } else { if (type == 1) grid.Rows[rowIdx].Cells["Prep"].Style.ForeColor = Color.Red; if (type==2) grid.Rows[rowIdx].Cells["Prep"].Style.ForeColor = Color.ForestGreen; } } 

但是当我进入UpdateGridSafe时,程序会挂起。

在调试器中,我看到grid.Invoke不会调用UpdateGridSafe。 请帮忙 – 出了什么问题?

编辑

经典线程创建代码

  Thread t = new Thread(new ParameterizedThreadStart(ThreadProc)); t.Start(dgvSource); t.Join(); MessageBox.Show("Done", "Info"); 

你有一个僵局。 你的t.Join阻止了GUI线程,直到ThreadProc完成。 ThreadProc被阻塞等待t.Join完成所以它可以执行Invokes。

坏代码

  Thread t = new Thread(new ParameterizedThreadStart(ThreadProc)); t.Start(dgvSource); t.Join(); <--- DEADLOCK YOUR PROGRAM MessageBox.Show("Done", "Info"); 

好的代码

  backgroundWorker1.RunWorkerAsync private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { var grid = (DataGridView)obj; foreach (DataGridViewRow row in grid.Rows) { if (Parser.GetPreparationByClientNameForSynonims(row.Cells["Prep"].Value.ToString()) != null) UpdateGridSafe(grid,row.Index,1); // don't need this Thread.Sleep(10); } } private void backgroundWorker1_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e) { MessageBox.Show("Done", "Info"); } 

编辑

也可以使用BeginInvoke而不是Invoke。 这样,每次更新GUI时,工作线程都不必阻塞。

参考

避免使用Invoke(),更喜欢BeginInvoke()

这是因为你加入了你的工作线程。 您的UI线程启动后台线程,然后调用Join。 这会阻止UI线程执行任何其他操作。

在此期间,后台线程正在执行其工作并调用Invoke,它等待UI线程响应。 因为UI线程正在等待Join,所以它不会处理要调用的请求。 因此,僵局。

你应该做的是消除Join和MessageBox。 将MessageBox放入自己的函数中。

 void NotifyDone() { if( InvokeRequired ) BeginInvoke( (MethodInvoker) NotifyDone ); else { // Perform any post-processing work MessageBox.Show("Done", "Info"); } } 

后台线程完成后,只需调用此方法(并从ThreadProc中消除静态)。

 private void ThreadProc(object obj) { var grid = (DataGridView)obj; foreach (DataGridViewRow row in grid.Rows) { if (Parser.GetPreparationByClientNameForSynonims(row.Cells["Prep"].Value.ToString()) != null) UpdateGridSafe(grid,row.Index,1); Thread.Sleep(10); } NotifyDone(); } 

就像其他人已经说过的那样,睡眠的使用,特别是在如此低的时间间隔,要么是危险的,误导性的,要么是毫无价值的。 我在算是一个毫无价值的阵营。

Invoke语句将一直等到主线程的消息泵不忙,并且可以处理新消息。 如果主线程忙,则Invoke将挂起。

在您的情况下,看起来您的顶级代码在紧密循环中运行,因此底部代码中的Invoke永远不可能实际运行。 如果你将上面的代码块中的Thread.Sleep更改为有时间的东西,希望这将使你的主线程有机会处理.Invoke调用。

根据您的主应用程序线程正在执行的操作,您可能需要在任何.Invoke调用运行之前实际完成第一个循环 – 如果是这种情况,我可以发布一些可能更好的修改后的代码。

从来没有,每次都使用Thread.Sleep(0)。 它没有做你认为它做的事情,除了痛苦之外什么都不会给你带来什么。 例如,在紧密循环中,OS可以决定刚刚睡眠的线程是下一个运行的线程。 因此,您实际上不会产生线程。

每N次迭代使用Thread.Sleep(1)再次尝试您的代码,其中N值约为0.25到1.0秒。

如果这不起作用,请告诉我,我们可以看看如何创建ThreadProc。

参考

永远不要在无限循环中睡眠(0)

编辑

从不使用Thread.Sleep的参数

Thread.Sleep是一个设计糟糕的程序的标志。

您也可能遇到从不同线程同时访问网格的问题。 DataTables不是线程安全的,所以我猜想DataGridView也不是。 下面是本文关于DataRow和Concurrency的一些示例代码,您可以使用Monitor.Enter和Montori.Exit来获得一些并发性。

  public void DoWorkUpdatingRow(object state) { List rowsToWorkOn = (List)state; foreach (DataRow dr in rowsToWorkOn) { Monitor.Enter(this); try { dr["value"] = dr["id"] + " new value"; } finally { Monitor.Exit(this); } } }