【问题标题】:Async method locks user interface异步方法锁定用户界面
【发布时间】:2022-12-17 17:50:07
【问题描述】:

据我所知,异步方法不会锁定用户界面。至少它似乎大部分时间都有效。但是这里没有,我不明白为什么。它是 Avalonia MVVM 应用程序。这是代码:

public class MainWindowViewModel : ReactiveValidationObject
{
   public MainWindowViewModel()
   {
      OnRunClick = ReactiveCommand.CreateFromTask(
                () => OnRun(),
                this.IsValid());
   }
   public ReactiveCommand<Unit, Unit> OnRunClick { get; }
   
   private async Task OnRun()
   {
      await Dispatcher.UIThread.InvokeAsync(() =>
      {
         DoSomethingForVeryLongTime();
      }
   }
}

单击按钮时调用OnRunClick,然后只要DoSomethingForVeryLongTime运行,用户界面就会停止响应。这是不可能发生的,因为异步确保接口仍然处于活动状态,但它发生了。

【问题讨论】:

  • 您根本没有使用异步方法。顾名思义,UIThread.InvokeAsync 在 UI 线程上运行 DoSomethingForVeryLongTimeDoSomethingForVeryLongTime 是什么,为什么要在 UI 线程上运行它?为什么不用await Task.Run(DoSomethingForVeryLongTime);
  • 您在 UI 线程上显式调用 DoSomethingForVeryLongTime,所以难怪 UI 会停止响应。
  • @PanagiotisKanavos await Task.Run 在桌面应用程序中不起作用,因为它会触发异常 - System.InvalidOperationException: Call from invalid thread
  • @Walter125 它运行得非常好——这是主要用例之一。 Web 应用程序已经为每个请求使用不同的线程。自 2012 年以来,几乎所有桌面应用程序都在使用它。Th。该错误是由于尝试从后台线程修改 UI 引起的,无论您如何调用该方法,这在任何操作系统中都是不允许的。你需要修改DoSomethingForVeryLongTime 所以它没有尝试修改用户界面。发布你的代码
  • @PanagiotisKanavos DoSomethingForVeryLongTime 不修改 UI,因为它是来自完全不同项目的算法。而且我很确定没有人会阅读 2300 行代码。

标签: c# .net wpf avalonia


【解决方案1】:

此代码在 UI 线程上运行 DoSomethingForVeryLongTime,而不是后台线程。它异步发出调用,但实际调用仍在 UI 线程上运行。

要实际在后台运行,请使用 Task.Run

private async Task OnRun()
{
   await Task.Run(DoSomethingForVeryLongTime);      
}

后台方法不能修改 UI,无论它们是如何调用的。执行需要以某种方式返回到 UI 线程。这就是await 首先所做的。

如果DoSomethingForVeryLongTime可以拆分成后台和UI部分,后台部分可以使用Task.Run在后台运行。执行将返回到带有await 的 UI 线程。例如

private async Task OnRun()
{
    await DoSomethingForVeryLongTime();      
}

async Task DoSomethingForVeryLongTime()
{
    for int(i=0;i<1000;i++)
    {
        //Process in another thread background
        await Task.Run(()=>DoSomethingExpensive(i));
        //Return to the UI thread and update it
        UpdateProgressBar(i);
    }
    lblStatus.Text="Complete";
}

【讨论】:

    【解决方案2】:

    您的问题是此处代码的这一部分:

    await Dispatcher.UIThread.InvokeAsync(() =>
    {
     DoSomethingForVeryLongTime();
    }
    

    您不希望在 UI 线程上执行长时间的同步操作,这会导致 UI 冻结。您希望在 UI 线程上执行尽可能少的操作。只做您需要做的事情,即运行与 UI 更新直接相关的代码。

    不应在 UI 上运行任何计算或长时间操作。计算结果可以在 UI 线程上使用,但计算本身不应该在 UI 线程上。

    此外,实际方法需要异步并在 InvokeAsync 方法调用中等待。

    更好的方法是这样的:

    private async Task OnRun()
    {
        DoSomethingForVeryLongTime();
        var requiredInfo = GetTheRequiredInformation();
        
        await Dispatcher.UIThread.InvokeAsync(() =>
        {
            await UseRequiredInfoToDoAQuickUpdateToTheUI(requiredInfo);
        }
    }
    

    【讨论】:

    • async 不会让任何东西在后台运行。此代码仍会阻止
    • @PanagiotisKanavos 是的,给出了一个快速的通用伪代码答案,但错过了。更新
    • 这里DoSomethingForVeryLongTime 将同步运行,所以我仍然在锁定界面上停留了几分钟。
    • @Walter125 它没有在 UI 线程上运行,所以它不会阻塞 UI。
    猜你喜欢
    • 1970-01-01
    • 2013-12-03
    • 1970-01-01
    • 2020-12-19
    • 2017-12-26
    • 1970-01-01
    • 2020-10-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多