使用线程分离UI

时间:2012-11-30 03:19:45

标签: c# multithreading

我是C#和线程的新手,我有一个项目,我有一个Reversi(othello)游戏,我必须做多线程,经过几天的实验,我没有进一步前进。我必须将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;
        }
    }

3 个答案:

答案 0 :(得分:2)

在Windows窗体中,您可以使用Control.Invoke Method来影响后台线程的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)。一旦代码尝试在线程之间共享一些变量,您就需要这个。

答案 1 :(得分: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);               
         }
    }      

基本上 - 任何时候你想要在另一个线程上旋转的方法的上下文中更新ui,你只需要通过根据需要调用委托void /方法来实现。

答案 2 :(得分:1)

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