Reputation: 123
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
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
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