【问题标题】:How to handle IO-heavy workload at WPF Application startup如何在 WPF 应用程序启动时处理 IO 繁重的工作负载
【发布时间】:2021-09-20 23:14:00
【问题描述】:

我目前正在尝试编写一个简单的 C#-WPF-Application 来作为一个简单的通用“启动器”。我们针对不同的应用程序进行编程。

其目的是检查“真实”软件的当前软件版本,如果有新版本可用,它会开始从网络共享复制安装程序,然后运行安装程序。 然后它启动“真正的”应用程序,仅此而已。

用户界面主要由一个启动窗口组成,它向用户显示当前执行的操作(版本检查、复制、安装、启动...)。

现在我在 App.cs 中重写的 StartUp 方法中创建我的视图和我的 viewModel

public override OnStartup(string[] args)
{
    var viewModel = new StartViewModel();
    var view = new StartView();

    view.DataContext = viewModel;

    view.Show();

    // HERE the logic for the Launch starts
    Task.Run(() => Launch.Run(args));
}

问题是,如果我不在这里异步,主线程将被阻塞,我无法更新 UI。因此我使用Task.Run(...) 让它工作。这解决了我阻塞 UI 线程的问题,但我对此有一些问题/疑问:

  1. 我不能等待任务,因为这会再次阻塞 UI。在哪里等待呢?
  2. 首先我在此处启动此工作流程的想法是否可行?

一些更新澄清:在我向用户展示 UI 后,我的逻辑开始执行繁重的 IO 工作。我想出的可能调用有以下 3 个变体:

view.Show();

// v1: completely blocks the UI, exceptions are caught
DoHeavyIOWork();

// v2: doesn't block the UI, but exceptions aren't caught
Task.Run(() => DoHeavyIOWork());

// v3: doesn't block the UI, exceptions are caught
await Task.Run(() => DoHeavyIOWork());

目前我不在我的工作电脑上,所以我很抱歉没有给你原始代码。这是一个即时创建的版本。

我猜 v1 和 v2 不好,因为异常和 UI 阻塞。

当我在办公室尝试 v3 时,我认为它不起作用。现在它似乎适用于我的本地示例。但是我真的不确定v3。因为我在那里使用async void StartUp(...)。这里可以吗?

【问题讨论】:

  • 我通常使用 ViewModel 参数制作一个 View ctor,而不是在 View 类之外设置 DataContext。此外,我将在您制作“view.Show()”之前运行该任务,我不调用“view.Show()”,而是调用“Run(view)”。但是,是的,看起来不错(至少对我来说)
  • 你为什么不能awaitTaskwould block the UI again 是什么意思?代码中的Task.Run() 正在线程池上排队一个新的Thread 并以即发即弃的方式执行它。您的代码不会等待 Launch.Run() 完成,因为您不是 awaiting Task.Run()。如果应该在OnStartup() 执行之后的代码之前完成Launch.Run(),这可能会导致问题。
  • 您似乎正在尝试重新发明ClickOnce 技术:"ClickOnce 应用程序可以自我更新。它们可以在新版本可用时检查并自动替换任何更新的文件. 根据安装类型,ClickOnce 提供了几个更新选项。应用程序可以配置为在启动时或启动后检查更新。" Deploy a .NET Windows desktop application using ClickOnce
  • 关于“ClickOnce”:在我的设置中,我们有许多机器运行我们的软件,但它用于生产设置,用户可以而且不应该接受或拒绝安装。此外,不同的机器有时必须运行不同版本的软件。我只是不知道 ClickOnce 是否是那里的最佳选择。

标签: c# wpf async-await task


【解决方案1】:

我不能等待任务,因为这会再次阻塞 UI。在哪里等呢?

await 不会阻止用户界面。在这里使用await 很好。

首先我在这里开始这个工作流程的想法是否可行?

我通常建议在执行任何异步操作时立即显示 一些 UI。然后当异步操作完成后,您可以更新/替换 UI。

【讨论】:

    【解决方案2】:

    感谢所有回复。 在阅读了您所有的 cmets 并结合了您的一些答案后,我想出了以下示例。它在我测试的所有情况下都有效。

    希望您的意见没有太大的错误。

    来自 App.xaml.cs 的代码

    public partial class App : Application
    {
        readonly StartViewModel viewModel = new StartViewModel();
    
        protected override async void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
    
            var view = new StartWindow
            {
                DataContext = viewModel
            };
    
            view.Show(); // alternative:  Run(view);
    
            // alternative: instead of calling it here it is possible
            //              to move these calls as callback into the Loaded of the view.
            await Task.Run(() => DoHeavyIOWork()); 
        }
            
        private string GenerateContent()
        {
            var content = new StringBuilder(1024 * 1024 * 100); // Just an example.
            for (var i = 0; i < 1024 * 1024 * 2; i++)            
                content.Append("01234567890123456789012345678901234567890123456789");
    
            return content.ToString();
        }
    
        private void DoHeavyIOWork()
        {
            var file = Path.GetTempFileName();
    
            for (var i = 0; i < 20; i++)
            {
                File.WriteAllText(file, GenerateContent());
                File.Delete(file);
    
                Dispatcher.Invoke(() => viewModel.Info = $"Executed {i} times.");
            }
        }
    }
    

    StartViewModel.cs 中的代码

    class StartViewModel : INotifyPropertyChanged
    {
        private string info;
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        public string Info
        {
            get => info; 
            set
            {
                info = value;
                OnPropertyChanged();
            }
        }
    
        private void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-25
      • 1970-01-01
      • 1970-01-01
      • 2012-08-15
      • 2012-03-10
      • 1970-01-01
      相关资源
      最近更新 更多