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

标签 c# multithreading asynchronous async-await task

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

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

  • 强制性的UI Thread , 运行用户界面
  • 和第二个Game Thread ,它在某种无限循环中运行,等待输入操作并以或多或少恒定的帧速率渲染游戏 View 。

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

  • 一个简单的MainFormCreate Cube按钮,一个Create Sphere按钮和渲染 View
  • 和自定义ChoiceForm要求用户在 Sharp Corners 之间进行选择和 Rounded Corners使用两个相应的按钮。

当用户点击 Create Cube按钮,UI Thread将处理此点击事件并(同步)排队一个新的 ()=>Game.Create<Cube>() Game Thread 处理的 Action .

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

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

如果这一切都发生在一个 UI Thread 上生活会相对轻松,Create方法看起来像这样:

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

经过反复试验,我提出了以下(实际有效的)解决方案,但每次我仔细观察它时,它似乎都会伤害到我内心的某个地方(尤其是 Task<Task<CornerChoice>> 方法中的 Create 部分):

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

最佳答案

阅读可能相关的问题后here和 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

关于c# - 如何等待来自不同线程的异步 UI 方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31115887/

相关文章:

c# - Visual Studio 2012 "the project file has been moved, renamed, or is not on your computer"

multithreading - 终止线程,退出前运行代码

java - 我该如何解决这个竞争条件?

c# - 围绕异步等待

kotlin - 从标签 Kotlin 的内部嵌套协程返回

c# - asp.net c# 防止在从服务器端代码更改索引时触发 selectedindexchanged 事件

c# - .NET 中是否有任何类型指示内部对象是否已初始化?

javascript - Node.js:for循环后执行

c# - 对于 Windows 应用程序,c# 比 delphi/realbasic 有什么优势

java - 如何在 Java 中识别监视器锁的所有者