如何等待来自不同线程的异步UI方法

本文关键字:线程 异步 方法 UI 何等待 等待 | 更新日期: 2023-09-27 18:00:23

我如何才能优雅地告诉我的应用程序,它应该等待某个异步(Ask())方法的结果,而不是在其当前(Game)线程上,而是在另一个(UI)线程上

我有一个带有两个线程的Forms应用程序

  • 运行用户界面的强制性UI Thread
  • 和第二个Game Thread,它以某种无限循环运行,等待输入动作并以或多或少恒定的帧速率呈现游戏视图

用户界面由两种形式组成:

  • 带有Create Cube按钮、Create Sphere按钮和渲染视图的简单MainForm
  • 以及要求用户使用两个相应按钮在CCD_ 10和CCD_

当用户点击Create Cube按钮时,UI Thread将处理此点击事件,并(同步)排队等待Game Thread处理新的()=>Game.Create<Cube>()动作。

Game Thread将在处理另一帧时获取该动作,并检查用户是否想要创建CubeSphere。如果用户请求Cube,则应该使用第二种形式询问用户关于立方体角的期望形状。

问题是,在等待用户决定时,UIGame线程都不应该被阻塞。因此,Task Game.Create<T>(...)方法和Task<CornerChoice> ChoiceForm.Ask()方法被声明为异步。Game Thread将等待Create<T>()方法的结果,而CCD_25又应在UI线程上等待Ask()方法的结果(因为ChoiceForm是在该方法内部创建和显示的)。

如果这一切都发生在一个UI Thread上,那么寿命将相对容易,Create方法将如下所示:

public class Game
{
    private async Task Create<IShape>()
    {
        CornerChoice choice = await ChoiceForm.Ask();
        ...
    }
}

经过一番尝试和错误,我想出了以下(实际可行的)解决方案,但每次我仔细观察它时,它似乎都会伤害我内心的某个地方(尤其是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;
        ...
    }
}

如何等待来自不同线程的异步UI方法

在阅读了这里的一个可能相关的问题和Stephen Dougs的博客文章Task.Run vs Task.Factory.StartNew(由Stephen Cleary链接),并与Mrinal Kamboj讨论了这个问题后,我得出结论,Task.Run方法在某种程度上是TaskFactory.StartNew的包装,适用于常见情况。因此,对于我不太常见的情况,我决定将引起疼痛的东西扫到一个扩展方法中,使调用看起来如下:

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

采用相应的扩展方法:

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);
    }
}

似乎也没有必要将() => ChoiceForm.Ask() lambda声明为async