窗体上的多线程和图像绘制问题

本文关键字:图像 绘制 问题 多线程 窗体 | 更新日期: 2023-09-27 18:03:11

我正在构建一个国际象棋游戏,我有一个循环,通过一个对象数组,并在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();
    }

每次按下"重放"按钮,该功能就会被触发。

在上面的方法中,我得到了一个竞争条件和两个线程相互碰撞。(同时进入循环).

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

游戏中真正发生的事情是这样的:

我按了一次重放键,所有的棋步重放都成功了。

当我再次按重播键时(重播结束后)…invalidoperation异常发生了,异常在PrintPieces循环方法的try catch中被捕获,然后出现一个文本框,其中包含如下stacktrace:

at System.Drawing.Image.get_FrameDimensionsList

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

我阅读了更多关于异常的信息…

系统。InvalidOperationException:对象当前正在使用中其他地方。

GDI+正在抱怨设备上下文(DC)正在尝试Use表示已经"在使用中"。对于winforms,这通常意味着有一个递归的图形。GetHdc必须首先匹配ReleaseHdcGetHdc .

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

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

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

MoveNext如果一个集合的对象在枚举数之后被修改创建.

我认为doWork事件处理方法中的循环需要保护…我需要在另一个RunAsync开始之前终止RunAsync .这证明是不成功的。

任何解决方案都会有帮助

注意:提供比我在这里提供的更多的代码不会增加任何东西,我花了一整天的时间来解决这个问题。我知道! !

窗体上的多线程和图像绘制问题

在调用RunWorkerAsync时禁用该按钮,并在完成处理程序和计算中重新启用它

在循环结束时,让DoWork方法完成—您不需要尝试取消它

然而,我怀疑问题在于你正在从工作线程操纵UI的事实(PrintPieces被工作线程调用)。您没有在print方法中向我们展示代码的样子,但我怀疑它没有进行线程封送

创建一个SynchronizationContext类型的成员变量(比如uiCtx),并在窗体加载事件中初始化它,像这样

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只是控件的包装。BeginImvoke等

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