Многопоточность и рисование изображения на задаче winform - PullRequest
1 голос
/ 22 июля 2011

Я строю шахматную игру, и у меня есть цикл, который проходит через массив объектов и рисует изображения шахматных фигур на формах вин (каждая графическая рамка представляет часть).

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

Вышеуказанный метод работает !!! У меня есть больше кода игры .. но здесь это неактуально ,,

Я также добавил функцию воспроизведения, в которой участвует фоновый работник.

   public void ReplayGame()
    {

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

    }

Функция запускается при каждом нажатии кнопки «воспроизведения».

В этом методе выше, где я получаю условие гонки и два потока сталкиваются друг с другом .. (войти в цикл одновременно).

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

        }

Что действительно происходит в игре, так это:

Я нажимаю кнопку воспроизведения один раз .. Воспроизведение всех шахматных ходов выполнено успешно.

Когда я снова нажимаю кнопку воспроизведения (после того, как воспроизведение завершено). Исключение: недопустимая операция. Исключение перехватывается при попытке try в методе цикла PrintPieces. И появляется текстовое поле с этой трассировкой стека:

at System.Drawing.Image.get_FrameDimensionsList

Ошибка происходит случайно после второго раза, когда я нажимаю кнопку воспроизведения .. (что предполагает условие запуска / несколько потоков, входящих в цикл).

Я прочитал больше об исключении ...:

System.InvalidOperationException: объект в настоящее время используется в другом месте.

GDI + жалуется, что контекст устройства (DC), который пытается использование уже "в использовании". С winforms это обычно означает, что есть рекурсивный Graphics.GetHdc должен соответствовать ReleaseHdc перед любым другим GetHdc.

Ошибка возникает, если вы рисуете форму из нескольких потоков. Вероятно, также возникнет исключение между потоками.

Потенциальное решение - не использовать несколько потоков при доступе к форме, включая потоки.

InvalidOperationException используется в случаях, когда не удается вызвать метод вызван причинами, отличными от недопустимых аргументов. За Например, InvalidOperationException вызывается:

MoveNext, если объекты коллекции изменены после перечислителя создан.

Я думаю, что цикл в методе обработчика событий doWork нуждается в защите ... Мне нужно завершить RunAsync до того, как запустится другая runAsync ... и это окажется неудачным ..

Любые решения помогут

ПРИМЕЧАНИЕ. Если вы предоставите больше кода, чем я вам предоставил, вы ничего не добавите ... Я работал над этим с ошибками целый день. Я знаю !!

1 Ответ

0 голосов
/ 22 июля 2011

Отключить кнопку при вызове RunWorkerAsync и повторно включить ее в обработчике завершения и при калькуляции

В конце цикла просто дайте завершить метод DoWork - вам не нужно пытаться отменить его

Однако я подозреваю, что проблема заключается в том, что вы манипулируете пользовательским интерфейсом из рабочего потока (рабочий поток вызывается PrintPieces). Вы не показываете, как выглядит код в методе print куска, но я подозреваю, что он не выполняет маршалинг потока

Создайте переменную-член типа SynchronizationContext (называемую, скажем, uiCtx) и инициализируйте ее в своем событии Loaded форм, например

uiCtx = SynchronizationContext.Current;

теперь в вашем методе Dowork измените его на следующий

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

     }

 }

Метод Post запускает лямбда-выражение в потоке пользовательского интерфейса

Обновление с более подробным объяснением того, что здесь происходит

В Windows вы не можете получить доступ к части пользовательского интерфейса из потока, отличного от того, который его создал - другими словами, вы не можете прикоснуться к пользовательскому интерфейсу из фонового потока. Я думаю, что происходило внутри фактического рисунка фрагмента, которым вы манипулировали контекстом устройства, чтобы нарисовать фрагмент, и основной поток также делал то же самое - так что вы получили свое исключение

SynchronizationContext - это независимая от фреймворка абстракция, которую можно использовать для запуска функциональности в «правильном» потоке. WinForms, WPF, Silverlight и ASP.NET имеют реализацию. WinForms one - это просто оболочка для Control.BeginImvoke и т. Д.

SynchronizationContext.Post принимает лямбда-выражение и получает эту лямбду для выполнения, в случае WinForms, в потоке пользовательского интерфейса. Это означает, что все ваши манипуляции с контекстом устройства теперь происходят в потоке пользовательского интерфейса, и поэтому вы не получите двух потоков, обращающихся к нему одновременно (так что ваше исключение исчезнет)

...