简体   繁体   English

如何从不同的线程等待异步UI方法?

[英]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? 我怎样才能优雅地告诉我的应用程序应该等待某些异步( Ask() )方法的结果不是在其当前( Game )线程上而是在另一个( UI )线程上呢?

I've got a Forms application with two threads 我有一个带有两个线程的Forms应用程序

  • the obligatory UI Thread , which runs the user interface 必需的UI Thread ,它运行用户界面
  • 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. 和第二个Game Thread ,它以一种无限循环运行,等待输入动作并以或多或少恒定的帧速率渲染游戏视图。

The user interface consists of two forms: 用户界面包含两种形式:

  • a simple MainForm with a Create Cube button, a Create Sphere button and a rendering view 一个简单的MainForm ,带有Create Cube按钮, Create Sphere按钮和渲染视图
  • and a custom ChoiceForm that asks the user to choose between Sharp Corners and Rounded Corners using two according buttons. 以及一个自定义的ChoiceForm ,要求用户使用两个按钮在Sharp CornersRounded Corners之间进行选择。

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 . 当用户单击“ Create Cube按钮时, UI Thread将处理此单击事件并(同步)对Game Thread处理的new ()=>Game.Create<Cube>()动作进行排队。

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 . Game Thread将在处理另一帧时检索此操作,并检查用户是否要创建CubeSphere And if the user requested a Cube it should ask the user using the second form about the desired shape for the cube corners. 如果用户请求Cube它应该询问用户使用第二种形式关于立方角的所需形状。

The problem is, that neither the UI nor the Game thread should be blocked while waiting for the user decision. 问题是,在等待用户决策时, UIGame线程都不应被阻止。 Because of this the Task Game.Create<T>(...) method and the Task<CornerChoice> ChoiceForm.Ask() methods are declared as async. 因此, Task Game.Create<T>(...)方法和Task<CornerChoice> ChoiceForm.Ask()方法被声明为异步。 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). Game Thread将等待Create<T>()方法的结果,而该方法又应等待UI线程上的Ask()方法的结果(因为ChoiceForm是在该方法内部创建并显示的)。

If this all would happen on a single UI Thread life would be relatively easy and the Create method would look like this: 如果这一切都发生在单个UI Thread生活将相对容易, Create方法将如下所示:

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): 经过一些试验和错误后,我提出了以下(实际工作)解决方案,但每次我仔细观察它时,它似乎都会伤到我内部(特别是Create方法中的Task<Task<CornerChoice>>部分):

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. 阅读可能与问题后这里和斯蒂芬另一部阿德日记的博客文章Task.Run VS Task.Factory.StartNew由Stephen Cleary的结合,并与Mrinal Kamboj讨论这个问题,我得出的结论是, Task.Run方法是那种包装的周围TaskFactory.StartNew用于常见情况。 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 . 似乎没有必要将() => ChoiceForm.Ask() lambda声明为async

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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