IronLionZion
IronLionZion

Reputation: 123

How to delay ICommand execution without causing InvalidOperationException?

I am programming a WPF 'Reversi' game in which players press on a tile in an 8x8 grid to place a stone.

This is the command to place a stone on a tile:

private class Click : ICommand
    {
        private readonly SquareViewModel squareViewModel;

        public ClickCommand(SquareViewModel squareViewModel)
        {
            this.squareViewModel = squareViewModel;

            squareViewModel.Square.IsValidMove.PropertyChanged += (sender, args) =>
            {
                if (CanExecuteChanged != null)
                {
  /*->*/            CanExecuteChanged(this, new EventArgs());
                }
            };
        }

        public event EventHandler CanExecuteChanged;

        public bool CanExecute(object parameter)
        {
            return squareViewModel.Square.IsValidMove.Value;
        }

        public void Execute(object parameter)
        {
            squareViewModel.Square.PlaceStone();
        }
    }

I have programmed an AI that places a stone when it's Player 2's turn:

void CurrentPlayer_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
          Player player = ((ICell<Player>)(sender)).Value;
          if (player != null && player.Equals(Player.TWO))
          {
               Vector2D nextMove = ai.FindBestMove(boardViewModel.Game);
               rowDataContexts[nextMove.Y].SquareDataContexts[nextMove.X].SquareViewModel.Click.Execute(null);
           }
        }
    }

This works perfectly fine. However, I want the ai to make a move after 2 seconds instead of immediately.

I have tried implementing a delay like this:

void CurrentPlayer_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
          Player player = ((ICell<Player>)(sender)).Value;
          if (player != null && player.Equals(Player.TWO))
          {
              Vector2D nextMove = ai.FindBestMove(boardViewModel.Game);
              Task.Delay(2000).ContinueWith(_ =>
                  {
                      rowDataContexts[nextMove.Y].SquareDataContexts[nextMove.X].SquareViewModel.Click.Execute(true);
                  });
          }
    }

But this results in an InvalidOperationException in ClickCommand at the line with CanExecuteChanged(this, new EventArgs()) (I have put an arrow at the relevant line in the first code sample). This occurs after 2 seconds (as soon as the Task.Delay continues).

How can I solve this?

Upvotes: 1

Views: 439

Answers (2)

luis_laurent
luis_laurent

Reputation: 814

Now with .Net framework 4.6.1 you can indicate the task to run the thread with in the current context by adding TaskScheduler.FromCurrentSynchronizationContext() parameter like below:

  Task.Delay(2000).ContinueWith(_ =>
  {
      rowDataContexts[nextMove.Y].SquareDataContexts[nextMove.X].SquareViewModel.Click.Execute(true);
  }, TaskScheduler.FromCurrentSynchronizationContext());

Upvotes: 1

Emond
Emond

Reputation: 50672

The exception is caused by executing the command on a non-UI thread because now it is part of a task which is executed by a thread from the thread pool.

To make it work switch to the UI thread either in the task or in the ViewModel.

Depending on the setup, if you are using a proper ViewModel, you might opt for raising the PropertyChanged on the UI thread in the ViewModel base class as most of the time the main reason to respond to that event is to update the UI. See https://stackoverflow.com/a/24882812/563088 on how to implement this.

Upvotes: 3

Related Questions