我正在编写一个回合制游戏,并且一直在使用异步方法来妥善控制连续动作。每当玩家被提示选择要交互的对象时,游戏管理器就会为该玩家(在玩家对象所附的脚本中)调用以下异步方法:
public async Task<List<GameObject>> SubmitObjs(List<GameObject> options, int selN)
{
List<GameObject> result = new List<GameObject>();
centralTime.ToggleOutlines(options, 1, true);
while (result.Count < selN)
{
selection = new TaskCompletionSource<GameObject>(TaskCreationOptions.RunContinuationsAsynchronously);
GameObject obj = await selection.Task; // 在这里等待TCS...
if (obj && options.Contains(obj))
if (result.Contains(obj))
{
result.Remove(obj);
centralTime.ToggleOutlines(new List<GameObject>() { obj }, 1, true);
}
else
{
result.Add(obj);
centralTime.ToggleOutlines(new List<GameObject>() { obj }, 2, true);
}
}
centralTime.OutlineOff();
return result;
}
为了允许玩家点击他们想要交互的对象,我在同一个脚本中的Update()
方法也编写了如下内容:
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
GameObject DO = DetectValObj();
if(DO) selection.TrySetResult(DO); // 在这里设置TCS...
}
}
如您所见,异步的SubmitObjs()
应该在等待Update
中通过TrySetResult()
调用恢复,以便当玩家点击对象时,它能继续处理玩家决定提交的对象。
然而,实际情况是,点击对象并运行selection.TrySetResult()
并不会恢复SubmitObjs()
中的处理。我已经测试过DetectValObj()
,并且它确实在将DO
正确传递给TrySetResult()
,更令人沮丧的是,在十次尝试中,大约有一次SubmitObjs()
会恢复并处理传入的对象。
老实说,我认为我没有完全理解TaskCompletionSource
和SetResult()
。我查阅了很多参考资料和示例,但我不明白为什么TrySetResult()
只在某些时候恢复等待的任务。
我也尝试过完全放弃TCS,而是使用一个简单的私有变量GameObject sel
,并在Update()
中如果没有分配sel
就使用Task.Yield()
:
while (!sel)
await Task.Yield();
obj = sel;
这个版本结果更糟,因为我发现这次Update()
根本没有被调用。
这里到底发生了什么,我该如何修复这个问题以使其按预期工作呢?
+编辑(2024-01-07): 我检查了SynchronizationContext
在Update()
和SubmitObjs()
中是否不同,它们都处于UnityEngine.UnitySynchronizationContext
之下。尝试使Update()
异步并使用await Task.Yield()
也被证明是无效的。
+编辑(2024-01-09): 根据Anton Tykhyy的建议,我尝试用System.Threading.Channels替换TaskCompletionSource的使用。检查SubmitObjs()
和Update()
是否使用相同的Channel<GameObject>
实例显示,它们实际上是不同的,尽管脚本范围内只有一个通道引用。通道的初始化除了在脚本中声明之外(声明为readonly
),没有其他地方。