我试图获得适当的‘结构’来监控一个游戏的状态从外部源(s)使用(Tasks)async/await,以便在一个无限循环中运行任务,然而它的当前编写方式似乎只是冻结了我的UI。
到目前为止我得到的是:
(在“状态机”类中)
// Start monitoring the game state for changes
public void Start()
{
tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
IsRunning = true;
task = Task.Factory.StartNew(async () =>
{
while (true)
{
await Task.Run(()=>CheckForStateChange());
await Task.Delay(1000); // Pause 1 second before checking state again
}
}, token, TaskCreationOptions.LongRunning, TaskScheduler.FromCurrentSynchronizationContext());
}
如果没有上面的“task.delay”行,UI将完全冻结。使用“task.delay”行,它不会冻结,但是如果我试图拖动窗口,它会跳回到我开始拖动它的地方。
我对当前代码的假设是'await task.run()'执行,在完成时'await task.delay()'执行,然后在完成时返回到while(true)无限循环的开头。(不平行运行的)。
CheckForStateChange()签名如下所示:
private void CheckForStateChange()
{
// ... A bunch of code to determine and update the current state value of the object
}
没什么特别的,简单的非异步方法。在StackOverflow上,我已经阅读了大量的示例/问题,我曾经将CheckForStateChange作为返回任务(在方法中包含可等待的操作)和许多其他代码迭代(具有相同的结果)。
最后,我从win32主表单(按钮)调用Start()方法,如下所示:
private void btnStartSW_Click(object sender, EventArgs e)
{
// Start the subscription of the event handler
if(!state.IsRunning)
{
state.StateChange += new SummonersWar.StateChangeHandler(OnGameStateChange);
state.Start();
}
}
我认为上面的代码是最简单的形式,我写的代码结构到目前为止,但显然它仍然没有写‘正确’。如有任何帮助,我们将不胜感激。
更新:发布服务器端(状态机类):
// ------ Publisher of the event ---
public delegate void StateChangeHandler(string stateText);
public event StateChangeHandler StateChange;
protected void OnStateChange() // TODO pass text?
{
if (StateChange != null)
StateChange(StateText());
}
其中StateText()方法只是检索当前状态的'text'表示形式的一种临时方法(在我将其组织成一个更整洁的结构之前,它实际上是一个占位符)
IsRunning纯粹是一个公共布尔。
和UI线程中的处理程序:
private void OnGameStateChange(string stateText)
{
// Game State Changed (update the status bar)
labelGameState.Text = "State: " + stateText;
}
UI冻结的原因
就主要问题而言:您已经通过
开始查找的最佳位置是
其他问题
您正在将指向UI
如果由于上述原因,您的外部任务碰巧在UI线程上执行,它不会真的绊倒您,因为内部调用被包装在
您正在将
public bool IsRunning
{
get
{
return task.Status == TaskStatus.Running;
}
}
public void Start()
{
tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
task = Task.Run(async () =>
{
while (true)
{
CheckForStateChange(token);
token.ThrowIfCancellationRequested();
await Task.Delay(1000); // Pause 1 second before checking state again
}
}, token);
// Uncomment this and step through `CheckForStateChange`.
// When the execution hangs, you'll know what's causing the
// postbacks to the UI thread and *may* be able to take it out.
// task.Wait();
}
因为您有一个
注意,我还提供了一个不同的
最后一句话
总体而言,整个解决方案感觉有点像一根拐杖,用来做一些应该更有反应性的事情--但我能想到这种设计是有效的场景。我只是不相信你的真的是其中之一。
编辑:如何找到阻止UI的东西
我会因为这个被否决而被遗忘,但我要说的是:
找到导致回发到UI线程的原因的可靠方法是与之死锁。这里有很多线程告诉您如何避免这种情况,但在您的情况下--我们会故意导致这种情况,并且您将确切地知道在轮询更改时需要避免哪些调用--尽管是否可能避免这些调用仍有待观察。
我在代码段的末尾放了一个