当前位置: 首页 > 知识库问答 >
问题:

C#等待任务+无限循环仍然冻结UI

太叔志尚
2023-03-14

我试图获得适当的‘结构’来监控一个游戏的状态从外部源(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;
}

共有1个答案

阎元徽
2023-03-14

UI冻结的原因

就主要问题而言:您已经通过 调用了 ,因此您的 不可能冻结UI,除非它包括被封送回UI线程的调用(即,显式使用的 ,或通过在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线程的原因的可靠方法是与之死锁。这里有很多线程告诉您如何避免这种情况,但在您的情况下--我们会故意导致这种情况,并且您将确切地知道在轮询更改时需要避免哪些调用--尽管是否可能避免这些调用仍有待观察。

我在代码段的末尾放了一个 指令。假设您在UI线程上调用 ,这将导致 中的某些内容出现死锁,您将知道需要解决的是什么。

 类似资料:
  • hasNext()的定义是“如果此扫描仪的输入中有另一个标记,则返回true。此方法可能会在等待输入扫描时阻塞。扫描仪不会前进超过任何输入。” 当我把 standardInput.hasNext() 放在 for 循环中时,程序会向无穷大运行。但是如果我把它放在 while-loop 中,它不会运行到无穷大。这两个程序之间的区别在哪里,为什么其中一个有效而另一个无效? for循环: while-l

  • 我有这样的代码: 我知道这不是做这件事的正确方法。以下是我认为可能是正确的方法: 不过,我只是想知道,为什么在运行第一个代码示例时,它会无限期地挂在上?此时代码执行到底发生了什么,即当线程在上阻塞时,什么代码“正在运行”?

  • 问题内容: 我想问这个问题,因为我不确定我是否正确使用了Node.js逻辑 我有一组ID,需要使用redis的get方法进行查询。在检查了某个值之后(假设我正在检查通过给定的“键”获取的对象是否具有空名称),然后将它们添加到列表中。这是我的示例代码; 但是由于Node的异步特性,正如预期的那样,如果不检查列表中的每个id,它将执行“ Goes here …”。我的需要是在检查每个id之后应用其余过

  • 我试图制作一个简单的应用程序来学习c(Visual Studio)中的一些东西。现在我正在制作一个音乐播放器,其中一个动作是在按钮点击事件中淡出音乐。 我对构建淡出部分没有问题,我做了一个while循环,每次循环运行时都将音量降低1%。此外,我还使用淡入度值更新标签。 唯一的问题是,为了减缓褪色,我正在使用线程。睡眠事件,该部分将冻结我的应用程序,并阻止使用淡入淡出值对我的文本标签进行任何更新。

  • 问题内容: 有没有一种方法可以停止执行无限循环的线程? 问题答案: 是的,您可以将替换(或在逻辑上)。 这样,当任务取消时,循环将终止。 循环看起来像这样: 使用应该是这样的:

  • 在C#中,我有以下两个简单的例子: 第一个示例创建一个打印“开始”的任务,等待5秒钟打印“完成”,然后结束任务。我等待任务完成,然后打印“全部完成”。当我运行测试时,它会按预期运行。 第二个测试应该具有相同的行为,只是由于使用了async和Wait,任务内部的等待应该是非阻塞的。但是这个测试只打印“开始”,然后立即打印“全部完成”和“完成”,永远不会打印。 我不知道我为什么会有这样的行为:S非常感