2011-07-22 52 views
1

我正在構建一個國際象棋遊戲,我有一個遍歷對象數組的循環,並在winforms上繪製棋子的圖像(每個圖片框代表一塊)。Winform問題上的多線程和繪圖圖像

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

    } 

該功能在每次「重放」按鈕按下時觸發。

在上面的方法中,我得到一個競爭條件和2個線程相互碰撞..(同時進入循環)。

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

     } 

確實發生在遊戲中這是什麼:

我按下重播按鈕once..The的所有棋被成功地重播。

當我再次按下重放按鈕(重播結束後)..異常無效操作異常發生..異常被捕獲在PrintPieces循環方法的try catch中。並且出現一個帶有此堆棧跟蹤的文本框:

at System.Drawing.Image.get_FrameDimensionsList 

這個錯誤我第二次按下按鈕重放..(這表明一個運行條件/多線程進入循環)之後隨機地發生。

我閱讀更多關於異常...:

System.InvalidOperationException:對象正在使用 別處。

GDI +抱怨試圖使用 的設備上下文(DC)已被「使用」。使用winforms通常意味着遞歸Graphics.GetHdc必須與任何其他 GetHdc之前的ReleaseHdc匹配。

如果您從多個線程繪製表單,則會發生此錯誤。 交叉線程異常也可能發生。

可能的解決方案是在訪問表單(包括線程)時不使用多個線程。

InvalidOperationException用於調用 方法失敗的原因是由無效參數以外的原因引起的情況。對於 示例,InvalidOperationException拋出如下:

如果在創建枚舉數 後修改了集合的對象,則MoveNext。

我認爲doWork事件處理程序方法中的循環需要保護......我需要在另一個runAsync啓動之前終止RunAsync..and證明不成功..

的任何解決方案,將有助於

注:提供了更多的代碼比我只要你在這裏難道不添加anything..I對竊聽一整天的工作。我知道!!

回答

0

禁用當你打電話的RunWorkerAsync並重新啓用它在你完成處理程序和calcenllation

在循環的結束只是讓DoWork的方法完成的按鈕 - 你不必試圖取消

但是,我懷疑問題在於您從工作線程操作UI的事實(PrintPieces由工作線程調用)。你不告訴我代碼在打印方法中的代碼是什麼,但我懷疑它沒有做線程編組

創建一個類型爲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方法,使UI線程上運行的lambda表達式

更新與這裏發生了什麼的多一點的解釋

在Windows中,你不能從比創建它的另外一個線程訪問了一塊UI的 - 換句話說,你不能從一個後臺線程觸摸UI 。我認爲發生的事情是在您操作設備上下文繪製該部分的實際繪圖的內部,並且主線程也在做同樣的事情 - 所以你得到了你的異常

SynchronizationContext是一個獨立於框架的抽象,你可以用來使功能在「正確」線程上運行。 WinForms,WPF,Silverlight和ASP.NET都有一個實現。 WinForms只是一個包裝Control.BeginImvoke,等

SynchronizationContext.Post需要一個lambda表達式並獲取該lambda執行,在WinForms的情況下,UI線程。這意味着你對設備上下文的所有操作現在都發生在UI線程上,所以你不會得到兩個線程同時訪問它(所以你的例外消失)

+0

我確實展示了該方法在打印方法中的外觀。看上面那個第一個方法..它打印pieces數組(一個所有棋子對象的數組..每個棋子在2維piecs數組中有一個位置..例如kingChessPiece對象是分片的[8,4 ] ..我會測試你的代碼,現在.. – Matrix001

+0

我也認爲線程工作的速度比winform能夠更快,更慢...缺少同步 – Matrix001

+0

哇,它不會引發異常。爲什麼它會起作用? – Matrix001