views:

332

answers:

6

Hello - I've got thread, which processes some analytic work.

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

I want safely update my gridView at loop, so I use classic way:

    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;

        }
    }

But when I enter UpdateGridSafe, the program hangs.

In the debugger, I see that grid.Invoke doesn't invoke UpdateGridSafe. Please help - what's wrong?

EDIT

Classic thread create code

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

The Invoke statement will wait until the main thread's message pump isn't busy, and can handle a new message. If your main thread is busy, the Invoke will hang.

In your case, it looks like your top code is running in a tight loop, so there's never a chance for the Invoke in the bottom code to actually run. If you change the Thread.Sleep in your upper code block to something with a time in it, hopefully that will give your main thread a chance to handle the .Invoke call.

Depending on what your main application thread is doing, you may need to actually finish your first loop before any of the .Invoke calls will run - if that's the case, I can post some modified code that might work better.

rwmnau
I change to Thread.Sleep(10); But got the same situation :(. Main app thread is ui thread. no working. Please can u post code that might work better.
Andrew Kalashnikov
@Andrew - if the main thread in the first code block is your UI thread, and you're running it inside a tight loop, then it won't respond to any .Invoke requests until it's done processing every single row in the forEach you're running. If you're calling ThreadProc() on a different thread, though, then your message pump should be available - the fact that the .Invoke is hanging says that you're blocking your main UI thread. Figure out where your main thread is stalled and resolve that, and your .Invoke calls will start working again.
rwmnau
I create ThreadProc from the main thrad, for processing some work at the background
Andrew Kalashnikov
@Andrew: Your edit clears it up. The .Join causes your UI thread to wait for your new thread in order to continue, which means it's busy when your secondary thread tries to call in .Invoke on it. Why do you need to Join it? Just wait for the thread you've created to finish, and then raise an event that you can handle on your UI thread to display your messagebox.
rwmnau
+1  A: 

Never, ever, every use Thread.Sleep(0). It doesn't do what you think it does and will cause you nothing but pain. For example, in a tight loop the OS may decide that the thread that just slept is the next one to run. As a result you won't actually yield the thread.

Try your code again using Thread.Sleep(1) every N iterations where N is about 0.25 to 1.0 seconds worth of work.

If that doesn't work let me know and we can look at how ThreadProc is created.

References

Never Sleep(0) in an Infinite Loop

EDIT

Argument for never using Thread.Sleep

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

Jonathan Allen
Thanks for the answer. I try set Sleep time, but it doesn't helps. Also I add some code to post.
Andrew Kalashnikov
+1  A: 

Why are you using thread.sleep?

Can you use the callback function here, if you want to perform any operation after updating the grid?

Ram
+3  A: 

It's because you're joining on your worker thread. Your UI thread starts the background thread, then calls Join on it. This stops the UI thread from performing any other actions.

During this, the background thread is doing its work and calling Invoke, which waits for the UI thread to respond. Because the UI thread is waiting for a Join, it won't ever process the request to invoke. Hence, deadlock.

What you should do, is eliminate the Join and the MessageBox. Put the MessageBox into its own function.

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

When the background thread is done, simply call this method (and eliminate static from 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();
    }  

And like everyone else has already said, the use of Sleep, especially at such a low interval is either dangerous, misleading or worthless. I'm in the count it worthless camp.

Adam Sills
+3  A: 

You have a deadlock. Your t.Join is blocking the GUI thread until ThreadProc is done. ThreadProc is blocked waiting for t.Join to finish so it can do the Invokes.

Bad Code

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

Good Code

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

EDIT

Also use BeginInvoke instead of Invoke. That way your worker thread doesn't have to block every time you update the GUI.

Reference

Avoid Invoke(), prefer BeginInvoke()

Jonathan Allen
+1  A: 

You also could be having issues accessing the grid at the same time from different threads. DataTables are not thread safe, so I'd guess that DataGridView isn't either. Here's some sample code from this article on DataRow and Concurrency where you would use Monitor.Enter and Montori.Exit to get some concurrency in place.

    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);
            }
        }
    }
Dan