Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Separating UI using threads

I'm new to C# and threading and i have project where i have a game of Reversi(othello) which i have to make multi-threaded, after experimenting for days I'm no further forward. I have to separate the UI from the code, basically so the processes which take a while to load do not 'freeze' the program, allowing the user to access the buttons and user interface bits.

I've looked into background worker, tasks, general threads.

Here's a couple of key sections of code, when i click the move button i want the dowork method to run, leaving the UI interactive.

    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;
        }
    }
like image 354
Hulaz Avatar asked Jul 07 '26 06:07

Hulaz


2 Answers

You want delegates.

Here's a fairly simple example of how you can go about doing this with delegates:

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

So basically - any time you want to update the ui from within the context of the method spun out on another thread, you simply make sure to do so by invoking the delegate void/method as needed.

like image 57
J.Wells Avatar answered Jul 09 '26 20:07

J.Wells


In Windows Forms you can use Control.Invoke Method to influence UI from background threads. You can either use lambda expressions, if there is no sence in making separate methods:

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

Or you can make a separate method:

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

and use it like:

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

I used Action Delegate to specify a delegate to the Invoke method. It fives you an opportunity to create any method signature you need.


Also have a look at Thread Synchronization (C# Programming Guide). You'll need this, as soon as your code tries to share some variables among threads.

like image 20
horgh Avatar answered Jul 09 '26 19:07

horgh



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!