【问题标题】:Separating UI using threads使用线程分离 UI
【发布时间】:2012-11-30 03:19:44
【问题描述】:

我是 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;
        }
    }

【问题讨论】:

  • 这是 WPF 还是 Windows 窗体?
  • 谢谢你,虽然我仍然无法将它付诸实践,但它看起来是一个不错的起点。

标签: c# multithreading


【解决方案1】:

你想要代表。

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

    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/方法来执行此操作。

【讨论】:

  • 仅供参考,不要传递 ex.Message。传递整个异常。里面通常有很多信息。如果需要记录字符串,只需记录 ex.ToString()。
  • 作了相应的注释和编辑。好电话,约翰!可以说,“this”限定词也可以取消。
  • 我投了赞成票,但如果您在致电HandleError 后添加“throw;”,我会更高兴。现在,即使异常非常糟糕,代码也会继续运行。
  • 我同意,但这更多的是实施问题。无论如何-对于所有可能阅读的人,请查看here for some gotchas about handling exceptions in multithreaded applications
【解决方案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)。只要您的代码尝试在线程之间共享一些变量,您就需要这个。

【讨论】:

    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-05-19
      • 2020-12-19
      • 2012-06-29
      • 1970-01-01
      • 2014-10-31
      • 1970-01-01
      • 2014-12-25
      相关资源
      最近更新 更多