开发者

Multithreading and drawing image on winform problem

开发者 https://www.devze.com 2023-03-21 23:37 出处:网络
I am building a chess game and i have a loop that runs through an array of objects and draws images of the chesspieces on winforms (Each picture box represents a piece).

I am building a chess game and i have a loop that runs through an array of objects and draws images of the chesspieces on winforms (Each picture box represents a piece).

 public  void PrintPieces(Pieces [,] pieces)
    {      
        for (int i = 1; i < 9; i++)
        {             
            for (int j = 1; j < 9; j++)
            { //pieces is an array of chess piece objects (pawn, king, bishop king etc)
                if (pieces[i, j] is Object )
                {
                    try
                    {
                        //The path of the image is obtained.
                        chessPics[i, j].Load(pieces[i, j].print());
                    }
                    catch (InvalidOperationException ex)
                    {
                        MessageBox.Show(ex.StackTrace);
                    }
                }
                else
                {  //chesspics is an array of pictureboxes
                    chessPics[i, j].Image = null;
                }
            }             
        }
    }

The above method works!!! I have more code of the game..but it is irrelevant here,,

I also added a replay function that involves a backgroundworker.

   public void ReplayGame()
    {

            backgroundWorker1.WorkerSupportsCancellation = true;
            backgroundWorker1.RunWorkerAsync();

    }

The function is triggered with every 'replay' button press.

In this method above where I get a race condition and 2 threads colliding with one another..(enter the loop simultaneously).

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {

              //replay is an array of the chess moves that were made since
the start of the game.
                        foreach (KeyValuePair<int, int[]> item in replay)// count should be more than 2
                        {
                            if (!backgroundWorker1.CancellationPending)
                            {
                                //There is more code here that wasnt presented 
                                //It basically executes each move from the replay
                                //array and return the chess pieces array (the positions
                                //of the current game after each move from teh replay array
                                //was executed)..The current game state returns in a form 
                                //of an array and returns from the method:                 
                                PrintPieces(codeFile.PieceState());
                            }
                            else
                            {
                                break;
                            }
                            System.Threading.Thread.Sleep(1000);
                        }
                //After the loop ends i am trying to cancel the operation of the background   //worker...but that seems useless.
                backgroundWorker1.CancelAsync();

        }

What really happens in game is this:

I press replay button once..The replay of all the chess moves is made successfully.

When I press the replay button again (after the replay is over)..The exception invalidoperation exception happens..the exception is caught in the try catch in teh PrintPieces loop method..And a text box appears with this stacktrace:

at System.Drawing.Image.get_FrameDimensionsList

The mistake happens randomly after the second time I press the button replay.. (which suggests a run condition/ multiple threads entering the loop).

I read more about the exception...:

System.InvalidOperationException: The object is currently in use elsewhere.

The GDI+ is complaining that the device context (DC) that is trying to use is already "in use". With winforms this usually means there is a recursive Graphics.GetHdc must match ReleaseHdc before anyh other GetHdc.

The error happens if you are drawing to a form from multiple threads. A cross-threading exception is likely to happen too.

Potential solution is not to use multiple threads when accessing a form, including threads.

InvalidOperationException is used in cases when the failure to invoke a method is caused by reasons other than invalid arguments. For example, InvalidOperationException is thrown by:

MoveNext if objects of a collection are modified after the enumerator is created.

What I think is that the loop in the doWork event handler method needs protection...I need to terminate the RunAsync before another runAsync starts..and that proves unsuccessful..

Any solutions would help

NOTE: Providing more code than I provided you here wouldnt add anything..I worked on that bugged for a whole day. I kn开发者_开发技巧ow!!


Disable the button when you call RunWorkerAsync and re-enable it in your completion handler and on calcenllation

At the end of the loop just let the DoWork method finish - you don't need to try to cancel it

However, I suspect that the problem lies with the fact you are manipulating the UI from the worker thread (PrintPieces is being called by the worker thread). You don;t show us what the code looks like in the print method of the piece but I suspect its not doing thread marshalling

Create a member variable of type SynchronizationContext (called say uiCtx) and initialize it in your forms Loaded event like this

uiCtx = SynchronizationContext.Current;

now in your Dowork method change it to the following

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    //replay is an array of the chess moves that were made since the start of the game.

    foreach (KeyValuePair<int, int[]> item in replay)// count should be more than 2
    {
         if (!backgroundWorker1.CancellationPending)
         {
              // ...
              uiCtx.Post(o =>
              {
                  PrintPieces(codeFile.PieceState());
              ), null);

              System.Threading.Thread.Sleep(1000);
         }
         else
         {
              break;
         }

     }

 }

The Post method makes the lambda expression run on the UI thread

Update with a bit more explanation of what's happening here

In Windows you cannot access a piece of UI from a thread other than the one that created it - in other words you cannot touch the UI from a background thread. What I think was happening was inside the actual drawing of the piece you were manipulating the device context to draw the piece and the main thread was also doing the same - so you got your exception

SynchronizationContext is an framework independent abstraction that you can use to make functionality run on the "right" thread. WinForms, WPF, Silverlight and ASP.NET all have an implementation. The WinForms one is simply a wrapper over Control.BeginImvoke, etc

The SynchronizationContext.Post takes a lambda expression and gets that lambda to execute on, in WinForms case, the UI thread. This means all of your manipulation of the device context now happens on the UI thread and so you will not get two threads accessing it concurrently (so your exception goes away)

0

精彩评论

暂无评论...
验证码 换一张
取 消