我正在使用 Godot 游戏引擎和 Mono/C# 开发一款游戏。 我正在努力实现以下目标:
- 在屏幕上显示消息
- 等待鼠标按钮点击/屏幕点击
- 显示另一条消息
- 等待点击
- ...
因此我有一个 Say()
方法:
async Task Say(string msg)
{
SetStatusText(msg);
_tcs = new TaskCompletionSource<Vector2>();
await _tcs.Task;
SetStatusText(string.Empty);
}
我期待的是这个工作:
async Task Foo()
{
// Displays "First".
await Say("First");
// "Second" should be shown after a click.
await Say("Second");
// "Third" should be shown after another click.
await Say("Third");
}
实际发生的情况是:
- 显示“第一个”
- 点击后会显示“第二个”。
- “第三个”永远不会出现,即使在点击之后也是如此。
我追踪到_tcs
为null
(或者处于无效状态,如果我没有将其设置为null
)我的鼠标按钮点击代码:
public void OnMouseButtonClicked(Vector2 mousePos)
{
if(_tcs != null)
{
_tcs.SetResult(mousePos);
_tcs = null;
return;
}
// Other code, executed if not using _tcs.
}
鼠标按钮单击代码设置_tcs
的结果,这对于第一个await
工作正常,但随后它失败了,尽管我正在创建一个新的实例TaskCompletionSource
每次调用 Say()
时。
Godot 问题还是我的 C# 异步知识变得如此生疏以至于我在这里遗漏了一些东西?几乎感觉 _tcs
正在被捕获并重用。
最佳答案
has my C# async knowledge become so rusty that I'm missing something here?
这是 await
的一个棘手的角落: 延续是同步安排的。我对此进行了更多描述on my blog并在 this single-threaded deadlock example .
关键要点是 TaskCompletionSource<T>
将在返回之前调用延续,这包括具有 await
的延续方法编辑了该任务。
遍历:
-
Foo
调用Say
第一次。 -
Say
等待_tcs.Task
,它不完整,因此它返回一个不完整的任务。 -
Foo
等待从Say
返回的任务,并返回一个未完成的任务。 - 用户点击并
OnMouseButtonClicked
被调用。 -
OnMouseButtonClicked
来电_tcs.SetResult
。这不仅完成了任务,还运行了任务的延续。 - 这意味着
Say
的剩余部分方法被执行。如果将断点放置在SetStatusText(string.Empty)
,你会看到线程堆栈有OnMouseButtonClicked
和SetResult
在里面! - 在
Say
的末尾方法,其任务已完成,并且该任务的延续被执行。 - 这意味着
Foo
继续执行 - 从内OnMouseButtonClicked
. -
Foo
来电Say
第二次,设置_tcs
并等待任务。由于该任务尚未完成,Say
返回未完成的任务。 -
Foo
等待该任务,返回OnMouseButtonClicked
. -
OnMouseButtonClicked
SetResult
之后继续执行生产线和套件_tcs
至null
.
这种synchronous continuation doesn't always happen ,但是一旦发生就很烦人。一种简单的解决方法是传递 TaskCreationOptions.RunContinuationsAsynchronously
到TaskCompletionSource<T>
构造函数。
关于c# - TaskCompletionSource 无法处理多个等待的任务 - 我做错了什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70565248/