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

How to handle IO-heavy workload at WPF Application startup

我目前正在尝试编写一个简单的 C#-WPF 应用程序,作为一个简单的通用 'launcher'。我们针对不同的应用程序进行编程。

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

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

现在我在 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 线程的问题,但我有一些 problems/questions 与此:

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

I cannot await the task, because that would block the UI again. Where to await it?

await 不会阻止 UI。这里用await就可以了

Is my concept of starting this workflow here ok in the first place?

我通常建议在进行任何异步操作时立即显示 some UI。然后当异步操作完成后,你可以 update/replace 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));
    }
}