关于winform问题的多线程和绘图图像

时间:2011-07-22 05:48:17

标签: c# winforms multithreading drawing

我正在建造一个国际象棋游戏,我有一个循环,贯穿一系列对象,并在winforms上绘制棋子的图像(每个图片框代表一块)。

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

        }

游戏中真正发生的是:

我按下重播按钮一次。所有国际象棋动作的重播都成功了。

当我再次按下重放按钮时(重放结束后)..异常无效操作异常发生..异常会在tep PrintPieces循环方法的try catch中捕获。此堆栈跟踪会出现一个文本框:

at System.Drawing.Image.get_FrameDimensionsList

第二次按下按钮重放后,错误会随机发生..(这表示运行条件/多个线程进入循环)。

我阅读了有关异常的更多信息......:

  

System.InvalidOperationException:该对象当前正在使用中   别处。

     

GDI +正在抱怨正在尝试的设备上下文(DC)   使用已经“在使用中”。使用winforms这通常意味着有一个   递归的Graphics.GetHdc必须在anyh之前匹配ReleaseHdc   GetHdc。

     

如果您从多个线程绘制到表单,则会发生错误。   也可能发生跨线程异常。

可能的解决方案是在访问表单时不使用多个线程,包括线程。

  

InvalidOperationException用于无法调用的情况   方法是由无效参数以外的原因引起的。对于   例如,抛出InvalidOperationException:

     如果在枚举器之后修改了集合的对象,则

MoveNext   已创建。

我认为doWork事件处理程序方法中的循环需要保护......我需要在另一个runAsync启动之前终止RunAsync并且证明不成功..

任何解决方案都有帮助

注意:提供比我在这里提供的代码更多的代码不会添加任何东西..我在那个问题上工作了一整天。我知道!!

1 个答案:

答案 0 :(得分: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方法使lambda表达式在UI线程上运行

通过更多解释此处发生的事情进行更新

在Windows中,您无法从创建它的线程以外的其他线程访问UI - 换句话说,您无法从后台线程触摸UI。我认为发生的事情是在你操作设备上下文的部分的实际绘图中绘制部分,主线程也是这样 - 所以你得到了例外

SynchronizationContext是一个独立于框架的抽象,可用于在“正确”的线程上运行功能。 WinForms,WPF,Silverlight和ASP.NET都有一个实现。 WinForms只是Control.BeginImvoke等的包装器

SynchronizationContext.Post接受一个lambda表达式并获取该lambda以在WinForms情况下执行UI线程。这意味着您对设备上下文的所有操作现在都发生在UI线程上,因此您不会同时访问它的两个线程(因此您的异常消失)