3

我是 C# 和线程的新手,我有一个项目,我有一个黑白棋游戏,我必须制作多线程,经过几天的试验,我没有进一步前进。我必须将 UI 与代码分开,基本上这样需要一段时间才能加载的进程不会“冻结”程序,从而允许用户访问按钮和用户界面位。

我研究了后台工作人员、任务、一般线程。

这是代码的几个关键部分,当我单击移动按钮时,我希望运行 dowork 方法,使 UI 保持交互。

    private void playerMoveButton_Click(object sender, EventArgs e)
    {
        _bw = new BackgroundWorker
        {
            WorkerReportsProgress = true,
            WorkerSupportsCancellation = true
        };
        _bw.DoWork += bw_DoWork;
        if (_bw.IsBusy) _bw.CancelAsync();
    }

  public void bw_DoWork(object sender, DoWorkEventArgs e)
    {

        if (gameOver)
        {
            moveLabel.Text = "Game Over";
            moveTypeLabel.Text = "";
            return;
        }

        // Now the computer plays
        moveLabel.Text = "My Move";

        // Does it have any legal moves?
        int numComputerMoves = theGame.CountLegalMovesMoves();
        if (numComputerMoves != 0)
        {
            if (computerPlayStyle == PlayStyle.RANDOM)
            {
                moveTypeLabel.Text = "Guessing...";

                moveTypeLabel.Visible = true;
                this.Refresh();
                // Sleep for a little to give player time to see what's
                // going on
                Thread.Sleep(1000);
                // get a move at random
                int[] movePos = theGame.FindRandomMove();
                // make move
                theGame.MakeMove(movePos[0], movePos[1]);
                boardLayoutPanel.Refresh();
            }
            else
            {
                moveTypeLabel.Text = "Thinking...";

                moveTypeLabel.Visible = true;
                this.Refresh();

                // Get best move
                int[] movePos = theGame.FindGoodMove(minimaxDepth);
                // make move
                theGame.MakeMove(movePos[0], movePos[1]);
                boardLayoutPanel.Refresh();
            }
        }
        else
        {
            moveTypeLabel.Text = "I've no legal moves.";

            moveTypeLabel.Visible = true;
            this.Refresh();
            // Sleep for a little to give player time to see what's
            // going on
            Thread.Sleep(1000);
            // Change current player
            theGame.SwapCurrentPlayer();
        }

        //Reset for the player move
        moveLabel.Text = "Your Move";

        int blackScore = theGame.BlackCellCount();
        int whiteScore = theGame.WhiteCellCount();

        string bscoreMsg = "Black: " + blackScore;
        string wscoreMsg = "White: " + whiteScore;

        blackScoreLabel.Text = bscoreMsg;
        whiteScoreLabel.Text = wscoreMsg;

        // Does player have any legal moves

        int numPlayerMoves = theGame.CountLegalMovesMoves();

        if (numPlayerMoves == 0)
        {
            moveTypeLabel.Text = "You have no legal moves.";
            playerMoveOKButton.Visible = true;
            // If computer player has no legal moves game over!
            if (numComputerMoves == 0)
            {
                gameOver = true;
            }
        }
        else
        {
            moveTypeLabel.Text = "Select cell or choose";
            randomMoveButton.Visible = true;
            playerMoving = true;
        }
    }
4

3 回答 3

2

在 Windows 窗体中,您可以使用Control.Invoke 方法从后台线程影响 UI。如果制作单独的方法没有意义,您可以使用 lambda 表达式:

public void bw_DoWork(object sender, DoWorkEventArgs e)
{
    string str;
    //count str value...
    textBox1.Invoke(new Action<string>((s) =>
                    {
                         textBox1.Text = s;
                    }),
                    str);
}

或者你可以制作一个单独的方法:

private void SetText(string s)
{
    textBox1.Text = s;
}

并像这样使用它:

textBox1.Invoke(new Action<string>(SetText), str);

我使用Action Delegate来指定Invoke方法的委托。它为您提供了创建所需的任何方法签名的机会。


另请查看Thread Synchronization (C# Programming Guide)。只要您的代码尝试在线程之间共享一些变量,您就需要这个。

于 2012-11-30T03:57:17.517 回答
2

你需要代表。

这是一个相当简单的示例,说明如何使用委托进行此操作:

    public partial class Form1 : Form
    {
        private Thread _worker;
        private delegate void SendErrorDelegate(Exception exception);
        private delegate void SetCurrentStatus(string status);
        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                 this._worker = new Thread(new ThreadStart(MyMethod));
                 this._worker.Start();
            }
            catch (Exception ex)
            {
                HandleError(ex);
            }
        }
        private void MyMethod()
        {
            //you can do work like crazy here, but any time you want to update UI from this method,
            //it should be done with a delegate method like:
            SetStatusLabel("this happened");
            //and
            SetStatusLabel("I just did something else");
        }
        private void SetStatusLabel(string status)
        {
           if (this.InvokeRequired)
           {
                this.Invoke(new SetCurrentStatus(SetStatusLabel), status);
                return;
           }
           this.lblStatusLable.Visible = true;
           this.lblStatusLabel.Text = status;
        }
        private void HandleError(Exception ex)
        {
             if (this.InvokeRequired)
             {
                this.Invoke(new SendErrorDelegate(HandleError), ex);
                return;
             }
             //update some UI element from delegate now. eg-
             this.txtExceptionBox.Text = ex.Message; //or ex.ToString() etc 
             //LogException(exception);               
         }
    }      

所以基本上 - 任何时候你想从另一个线程上的方法的上下文中更新用户界面,你只需确保通过根据需要调用委托 void/方法来做到这一点。

于 2012-11-30T04:05:36.880 回答
1

两个步骤应该让你更接近:
1) 将需要更新 UI 的任何代码分离到单独的方法中。
2) 从你的 DoWork 方法中,当你想调用一个方法来更新 UI 时,使用Dispatcher.Invoke来调用将要更新 UI 的方法。这可确保该方法在 UI 的线程中执行。

于 2012-11-30T03:28:12.037 回答