【问题标题】:Why does user interface still freeze?为什么用户界面仍然冻结?
【发布时间】:2021-09-02 03:12:02
【问题描述】:

我试图防止在等待时冻结应用程序界面,所以我编写了这个函数,但它不起作用。

错误在哪里,我该如何解决?

async void buttonBlocking()
{
    await Task.Run(() =>
    {
        if (irTryCounter % 3 == 0)
        {
            this.Dispatcher.Invoke(() =>
            {
                grdLogin.IsEnabled = false;
                Thread.Sleep(10000);
                grdLogin.IsEnabled = true;
            });
        }
    });
}

【问题讨论】:

  • 你也可以在 lambda 中使用async/await 来调用Task.Delay,但这很奇怪。使用计时器?
  • this.Dispatcher.Invoke() 将在 UI 线程的上下文中运行该代码,因此睡眠将停止 UI 线程。
  • 还有related
  • 除了@MatthewWatson 的好评之外,请注意Dispatcher.Invoke 不仅会导致 UI 线程死锁,还会导致线程池线程死锁。因此,请考虑使用Dispatcher.BeginInvoke。当然你会想把 Thread.Sleep 移到别处

标签: c# multithreading xaml async-await


【解决方案1】:

如果您的 buttonBlocking 是从 UI 线程调用的,那么您可以简化代码:

async void buttonBlocking()
{
  if (irTryCounter % 3 == 0)
  {
    grdLogin.IsEnabled = false;
    await Task.Delay(10000);
    grdLogin.IsEnabled = true;
  }
}

这样它就不会阻塞你的用户界面

【讨论】:

  • 不要做async void - 除非是事件处理程序,否则总是做async Task
【解决方案2】:

正如 Matthew Watson 在 a comment 中指出的那样,错误是 Dispatcher.Invoke() 将在 UI 线程上运行代码,因此 Thread.Sleep 将阻塞 UI 线程。

现在让我们看看如何解决这个问题。我假设您的代码是一个事件处理程序,并且您希望该处理程序异步运行。这是使用async void 方法的唯一有效方案。在任何其他情况下,您都应该使用async Task

private async void Button1_Click(object sender, RoutedEventArgs e)
{
    if (irTryCounter % 3 == 0)
    {
        grdLogin.IsEnabled = false;
        await Task.Run(() =>
        {
            // Here you can run any code that takes a long time to complete
            // Any interaction with UI controls is forbidden here
            // This code will run on the thread-pool, not on the UI thread
            Thread.Sleep(10000);
        });
        grdLogin.IsEnabled = true;
    }
}

如果长时间运行的操作返回需要传递给 UI 线程的结果,请使用返回 Task<TResult>Task.Run 重载:

int result = await Task.Run(() =>
{
    Thread.Sleep(10000);
    return 13;
});
// We are back on the UI thread, and the result is available for presentation

我假设Thread.Sleep(10000) 是一个需要很长时间才能完成的实际同步操作的占位符。如果可以异步执行此操作会更好,因为它不需要在整个操作期间消耗ThreadPool 线程。例如,可以使用Task.Delay 方法异步执行延迟:

await Task.Run(async () =>
{
    await Task.Delay(10000);
});

在这种情况下,Task.Run 包装器可能看起来多余,但实际上it's not。将您的异步代码包装在 Task.Run 中可以保护您的应用程序免受阻塞当前线程的不良异步 API 的影响。包含包装器的开销很小,并且在 WinForms/WPF 应用程序中应该没有明显的影响。 ASP.NET 应用程序例外,Task.Runing 是不可取的。

【讨论】:

  • 感谢您更新您的答案,我认为它现在通过详细的解释读起来好多了,我特别喜欢演练风格.. :)
猜你喜欢
  • 1970-01-01
  • 2016-07-22
  • 2017-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-08
相关资源
最近更新 更多