views:

332

answers:

3

I have a PictureBox control on a Form that is supposed to draw something every 100ms.

The background thread performs some calculation in a loop and after every iteration, it fires an event.

Edited (as a reply to a comment):

World world = new World();

void CreateBackgroundThread() {
    Thread backgroundThread = new Thread(world.BackgroundWorkerFunction);
    backgroundThread.Start();
}

 

public class World { 

    void BackgroundWorkerFunction() {
        IPiece piece = PieceFactory.Create();
        for (int i = 0; i < stepsCount; i++) {
            piece.Calculate();
            if (OnPieceStep != null) {
                OnPieceStep(piece);
            }
        }
    }

}

Inside the main form, there is a handler, set by:

world.OnPieceStep += DrawScreen;

and the Invoke wrapper (since the control to be drawn to, is created in the UI thread).

void DrawScreen(IPiece piece) {
    this.Invoke(new PieceStep(_drawScreen), piece);
}
void _drawScreen(IPiece piece) {
    drawer.Draw(world.Board, piece);
}

Now, I'd like the for loop to pause 100ms after each iteration, so I added Thread.Sleep(100); before firing an event:

for (int i = 0; i < stepsCount; i++) {
    IPiece piece = Calculate();
    if (OnPieceStep != null) {
        Thread.Sleep(100);
        OnPieceStep(piece);
    }
}

This however does not refresh the pictureBox every 100 ms, but draws only the last iteration of the loop, and only after the loop finishes.

Shouldn't Thread.Sleep pause the thread it is called on, not the UI thread?   Update: I have just tried to click on the app while the background thread calculates. The program blocks ("not responding"), so the Thread.Sleep was obviously called on UI Thread. Is this expected behaviour or something is wrong with my threading?

+1  A: 

From threading prospective everything looks fine. Most likely problem relies within _drawScreen(IPiece piece) method. Have you tried to refresh/update the window after drawing?

Some insight: Control.Invoke method passes delegate supplied as a parameter as a window message using Win32 SendMessage function.

Vitaliy Liptchinsky
Shouldn't Thread.Sleep pause the thread on which is called? (Note that the for loop described is not working on UI Thread.)I can't use timer for this, because it is not a timed event, but depends on calculation. The only thing I want is that after the calculation is finished, the worker thread pauses 100 ms and then fires an event. The duration of the calculation can be even a minute, so I can't use the timer that fires every 100 ms. The only way I can use the Timer is to postpone firing an event for 100 ms, but I thought there was a code-cleaner way.
kornelijepetak
_drawScreen() only creates a blank bitmap, gets a Graphics.FromImage, draws some circles and then sets the pictureBox.Image to the bitmap. This is all done in UI thread (because of Invoke) so I don't see why it shouldn't draw after each iteration, but only after the whole loop has finished.
kornelijepetak
A: 

Are you invalidating your picturebox after you do the drawing?

Simon
Not explicitely, but I just tried explicitely invalidate and the result is the same.
kornelijepetak
A: 

As stated on this page:

Thread.Sleep is unique amongst the blocking methods in that suspends Windows message pumping within a Windows Forms application, or COM environment on a thread for which the single-threaded apartment model is used. This is of little consequence with Windows Forms applications, in that any lengthy blocking operation on the main UI thread will make the application unresponsive – and is hence generally avoided – regardless of the whether or not message pumping is "technically" suspended. The situation is more complex in a legacy COM hosting environment, where it can sometimes be desirable to sleep while keeping message pumping alive. Microsoft's Chris Brumme discusses this at length in his web log (search: 'COM "Chris Brumme"').

It seems that Thread.Sleep() in WinForms always pauses the UI, no matter in which thread is called.

kornelijepetak