【问题标题】:c# Thread issue using Invoke from a background threadc# 使用从后台线程调用的线程问题
【发布时间】:2010-06-04 03:48:10
【问题描述】:

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

   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");

【问题讨论】:

  • 你为什么使用thread.sleep?如果要在更新网格后执行任何操作,可以在这里使用回调函数吗?

标签: c# .net multithreading


【解决方案1】:

你有一个死锁。在 ThreadProc 完成之前,您的 t.Join 会阻塞 GUI 线程。 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 时阻塞。

参考

Avoid Invoke(), prefer BeginInvoke()

【讨论】:

    【解决方案2】:

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

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

    你应该做的是消除 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();
        }  
    

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

    【讨论】:

      【解决方案3】:

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

      在您的情况下,您的顶部代码似乎在一个紧密的循环中运行,因此底部代码中的 Invoke 永远没有机会实际运行。如果您将上部代码块中的 Thread.Sleep 更改为包含时间的内容,希望这将使您的主线程有机会处理 .Invoke 调用。

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

      【讨论】:

      • 我改成 Thread.Sleep(10);但是遇到了同样的情况:(。主应用程序线程是 ui 线程。没有工作。请你发布可能更好的代码。
      • @Andrew - 如果第一个代码块中的主线程是你的 UI 线程,并且你在一个紧密的循环中运行它,那么它不会响应任何 .Invoke 请求,直到它完成处理你正在运行的 forEach 中的每一行。但是,如果您在不同的线程上调用 ThreadProc(),那么您的消息泵应该可用 - .Invoke 挂起的事实表明您正在阻塞主 UI 线程。找出你的主线程在哪里停止并解决它,你的 .Invoke 调用将再次开始工作。
      • 我从主线程创建 ThreadProc,用于在后台处理一些工作
      • @Andrew:您的编辑清除了它。 .Join 会导致您的 UI 线程等待您的新线程才能继续,这意味着当您的辅助线程尝试调用 .Invoke 时它很忙。为什么需要加入它?只需等待您创建的线程完成,然后引发您可以在 UI 线程上处理的事件以显示您的消息框。
      【解决方案4】:

      永远,永远,每次都使用 Thread.Sleep(0)。它不会像你认为的那样做,只会给你带来痛苦。例如,在一个紧密循环中,操作系统可能会决定刚刚休眠的线程是下一个要运行的线程。结果,您实际上不会产生线程。

      每 N 次迭代使用 Thread.Sleep(1) 再次尝试您的代码,其中 N 大约需要 0.25 到 1.0 秒的工作时间。

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

      参考文献

      Never Sleep(0) in an Infinite Loop

      编辑

      从不使用 Thread.Sleep 的论据

      Thread.Sleep is a sign of a poorly designed program.

      【讨论】:

      • 感谢您的回答。我尝试设置睡眠时间,但它没有帮助。我还添加了一些代码来发布。
      【解决方案5】:

      您也可能在从不同线程同时访问网格时遇到问题。 DataTables 不是线程安全的,所以我猜 DataGridView 也不是。这是来自this article on DataRow and Concurrency 的一些示例代码,您可以在其中使用 Monitor.Enter 和 Montori.Exit 来实现一些并发。

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

      【讨论】:

        猜你喜欢
        • 2011-10-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-18
        相关资源
        最近更新 更多