How can I elegantly tell my application that it should await the result of some async (Ask()) method not on its current (Game) thread but on a different (UI) thread instead?
I've got a Forms application with two threads
- the obligatory
UI Thread, which runs the user interface - and a second
Game Thread, that runs in a sort of infinite loop, waiting for input actions and rendering the game view at a more or less constant framerate.
The user interface consists of two forms:
- a simple
MainFormwith aCreate Cubebutton, aCreate Spherebutton and a rendering view - and a custom
ChoiceFormthat asks the user to choose betweenSharp CornersandRounded Cornersusing two according buttons.
When the user clicks on the Create Cube button, the UI Thread will handle this click event and (synchronously) queue a new ()=>Game.Create<Cube>() action to be processed by the Game Thread.
The Game Thread will fetch this action when it is processing another frame and check if the user wanted to create a Cube or a Sphere. And if the user requested a Cube it should ask the user using the second form about the desired shape for the cube corners.
The problem is, that neither the UI nor the Game thread should be blocked while waiting for the user decision. Because of this the Task Game.Create<T>(...) method and the Task<CornerChoice> ChoiceForm.Ask()methods are declared as async. The Game Thread will await the result of the Create<T>() method, which in its turn should await the result of the Ask() method on the UI thread (because the ChoiceForm is created and displayed right inside of that method).
If this all would happen on a single UI Thread life would be relatively easy and the Create method would look like this:
public class Game
{
private async Task Create<IShape>()
{
CornerChoice choice = await ChoiceForm.Ask();
...
}
}
After some trial and error I came up with the following (actually working) solution, but it seem to hurt me somewhere inside each time I look at it closely (especially the Task<Task<CornerChoice>> part in the Create method):
public enum CornerChoice {...}
public class ChoiceForm
{
public static Task<CornerChoice> Ask()
{
...
}
}
public class MainForm
{
private readonly Game _game;
public MainForm()
{
_game = new Game(TaskScheduler.FromCurrentSynchronizationContext());
}
...
}
public class Game
{
private readonly TaskScheduler _uiScheduler;
public Game(TaskScheduler uiScheduler)
{
_uiScheduler = uiScheduler;
}
private async Task Create<IShape>()
{
...
Task<CornerChoice> task = await Task<Task<CornerChoice>>.Factory.StartNew(
async () => await ChoiceForm.Ask(),
CancellationToken.None, TaskCreationOptions.None, _uiScheduler);
CornerChoice choice = await task;
...
}
}