简体   繁体   中英

How to await an async UI method from a different thread?

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 MainForm with a Create Cube button, a Create Sphere button and a rendering view
  • and a custom ChoiceForm that asks the user to choose between Sharp Corners and Rounded Corners using 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;
        ...
    }
}

After reading a probably related question here and Stephen Dougs blog post Task.Run vs Task.Factory.StartNew linked by Stephen Cleary and discussing this question with Mrinal Kamboj I was led to the conclusion that the Task.Run method is sort of a wrapper around TaskFactory.StartNew for the common cases. So for my not-so-common case I decided to sweep the pain-causing stuff into an extension method to make the invocation look like following:

private async Task Create<IShape>()
{
    ...
    CornerChoice choice = await _uiScheduler.Run(ChoiceForm.Ask);
    ...
}

With the according extension method:

public static class ExtensionsForTaskScheduler
{
    public static async Task<T> Run<T>(this TaskScheduler scheduler,
        Func<Task<T>> scheduledTask)
    {
        return await await Task<Task<T>>.Factory.StartNew(scheduledTask,
            CancellationToken.None, TaskCreationOptions.None, scheduler);
    }
}

It seems that it was also not necessary to declare the () => ChoiceForm.Ask() lambda as async .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM