[英]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应用程序
UI Thread
, which runs the user interface UI Thread
,它运行用户界面 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: 用户界面包含两种形式:
MainForm
with a Create Cube
button, a Create Sphere
button and a rendering view MainForm
,带有Create Cube
按钮, Create Sphere
按钮和渲染视图 ChoiceForm
that asks the user to choose between Sharp Corners
and Rounded Corners
using two according buttons. ChoiceForm
,要求用户使用两个按钮在Sharp Corners
和Rounded 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
将在处理另一帧时检索此操作,并检查用户是否要创建Cube
或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. 如果用户请求
Cube
它应该询问用户使用第二种形式关于立方角的所需形状。
The problem is, that neither the UI
nor the Game
thread should be blocked while waiting for the user decision. 问题是,在等待用户决策时,
UI
和Game
线程都不应被阻止。 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.