如何在WPF应用程序启动时处理IO繁重的工作负载



我目前正在尝试编写一个简单的C#-WPF应用程序,它的功能就像一个简单通用的"启动器"。针对不同的应用程序,我们进行编程。

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

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

现在,我在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(...)。这里可以吗?

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

await不会阻塞UI。在这里使用await是可以的。

我在这里启动此工作流的概念一开始可以吗?

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

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

希望你的观点没有太大的错误。

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));
}
}

最新更新