如何等待来自不同线程的异步 UI 方法?
How to await an async UI method from a different thread?
我如何优雅地 告诉我的应用程序它应该等待某些异步 (Ask()
) 方法的结果,而不是在其当前 (Game
) 线程而是在不同的 (UI
) 线程上?
我有一个带有两个线程的 Forms 应用程序
- 运行用户界面的强制性
UI Thread
- 和第二个
Game Thread
,它以一种无限循环的方式运行,等待输入操作并以或多或少恒定的帧速率渲染游戏视图。
用户界面由两种形式组成:
- 一个简单的
MainForm
,带有一个 Create Cube
按钮、一个 Create Sphere
按钮和一个渲染视图
- 和自定义
ChoiceForm
,要求用户使用两个相应的按钮在 Sharp Corners
和 Rounded Corners
之间进行选择。
当用户单击 Create Cube
按钮时,UI Thread
将处理此单击事件并(同步)排队一个新的 ()=>Game.Create<Cube>()
操作以供 Game Thread
.
Game Thread
将在处理另一帧时获取此操作,并检查用户是否要创建 Cube
或 Sphere
。如果用户请求 Cube
,它应该询问使用第二种形式的用户关于立方体角的所需形状。
问题是,在等待用户决定时,UI
和 Game
线程都不应该被阻塞。因此,Task Game.Create<T>(...)
方法和 Task<CornerChoice> ChoiceForm.Ask()
方法被声明为异步的。 Game Thread
将等待 Create<T>()
方法的结果,而 Create<T>()
方法又应该等待 UI 线程上的 Ask()
方法的结果(因为 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;
...
}
}
在阅读了 Stephen Cleary 链接的一个可能相关的问题 here and Stephen Dougs blog post Task.Run vs Task.Factory.StartNew 并与 Mrinal Kamboj 讨论了这个问题后,我得出的结论是 Task.Run
方法是 [=13] 的包装器=] 对于常见情况。因此,对于我不太常见的情况,我决定将引起痛苦的东西扫到扩展方法中,使调用看起来如下所示:
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
。
我如何优雅地 告诉我的应用程序它应该等待某些异步 (Ask()
) 方法的结果,而不是在其当前 (Game
) 线程而是在不同的 (UI
) 线程上?
我有一个带有两个线程的 Forms 应用程序
- 运行用户界面的强制性
UI Thread
- 和第二个
Game Thread
,它以一种无限循环的方式运行,等待输入操作并以或多或少恒定的帧速率渲染游戏视图。
用户界面由两种形式组成:
- 一个简单的
MainForm
,带有一个Create Cube
按钮、一个Create Sphere
按钮和一个渲染视图 - 和自定义
ChoiceForm
,要求用户使用两个相应的按钮在Sharp Corners
和Rounded Corners
之间进行选择。
当用户单击 Create Cube
按钮时,UI Thread
将处理此单击事件并(同步)排队一个新的 ()=>Game.Create<Cube>()
操作以供 Game Thread
.
Game Thread
将在处理另一帧时获取此操作,并检查用户是否要创建 Cube
或 Sphere
。如果用户请求 Cube
,它应该询问使用第二种形式的用户关于立方体角的所需形状。
问题是,在等待用户决定时,UI
和 Game
线程都不应该被阻塞。因此,Task Game.Create<T>(...)
方法和 Task<CornerChoice> ChoiceForm.Ask()
方法被声明为异步的。 Game Thread
将等待 Create<T>()
方法的结果,而 Create<T>()
方法又应该等待 UI 线程上的 Ask()
方法的结果(因为 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;
...
}
}
在阅读了 Stephen Cleary 链接的一个可能相关的问题 here and Stephen Dougs blog post Task.Run vs Task.Factory.StartNew 并与 Mrinal Kamboj 讨论了这个问题后,我得出的结论是 Task.Run
方法是 [=13] 的包装器=] 对于常见情况。因此,对于我不太常见的情况,我决定将引起痛苦的东西扫到扩展方法中,使调用看起来如下所示:
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
。