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

时间:2010-06-04 03:48:10

标签: c# .net multithreading

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

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

5 个答案:

答案 0 :(得分:6)

你有一个僵局。你的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时,工作线程都不必阻塞。

参考

Avoid Invoke(), prefer BeginInvoke()

答案 1 :(得分:4)

这是因为你加入了你的工作线程。您的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();
    }  

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

答案 2 :(得分:1)

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

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

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

答案 3 :(得分:1)

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

每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.

答案 4 :(得分:1)

您也可能遇到从不同线程同时访问网格的问题。 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);
            }
        }
    }