在 WPF 的另一个对话框中运行繁重任务时运行启动对话框

Posted

技术标签:

【中文标题】在 WPF 的另一个对话框中运行繁重任务时运行启动对话框【英文标题】:Running splash dialog while running heavy task in another dialog in WPF 【发布时间】:2021-01-20 08:49:17 【问题描述】:

我正在尝试执行标题中描述的任务。 HeaviWindow 执行一些繁重的任务,因此在任务完成之前不会显示。这次我想用动画加载器显示一个启动画面。

到目前为止,我使用了以下构造:

private RunHeavyTask()

    ShowSplashScreen("Please wait...");
    var dialog = new HeaviWindow();
    dialog.ShowDialog();


private void ShowSplashScreen(string str)

    Thread newWindowThread = new Thread(new ParameterizedThreadStart(ThreadStartingPoint));
    newWindowThread.SetApartmentState(ApartmentState.STA);
    newWindowThread.IsBackground = true;
    newWindowThread.Start(str);


private void ThreadStartingPoint(object str)

    splash = new WaitWindow((string)str);
    splash.Show();
    System.Windows.Threading.Dispatcher.Run();

但突然发现,在运行线程时,所有其他后台线程都停止工作。

我已尝试为此目的使用Task

private void ShowSplashScreen(string str)
    Task.Run(() => 
    
        Application.Current.Dispatcher.Invoke(delegate
        
            splash = new WaitWindow((string)str);
            splash.ShowDialog();
        );
    );

但在繁重的对话框完成任务之前不会显示启动画面。如果我使用BackgroundWorker,结果相同。

所以我的问题 - 我如何在另一个任务中运行繁重的任务时显示启动对话框。启动对话框使用了一些动画,因此需要更新。

【问题讨论】:

But splash screen not displayed until the heavy dialog finish the task 调用ShowDialog 会阻塞执行直到它完成,这是正常的。 @Çöđěxěŕ 确切地说,问题是我怎样才能避免这种情况。 这里更重要的是你的HeaviWindow 中发生了什么,一个对话框需要在 UI 线程中显示,但是如果你的HeaviWindow 正在执行它是在 UI 线程上工作,或者它是根本不使用线程,那么您启动新线程的调用必须等待 HeaviWindow 释放 UI 线程以显示启动屏幕。 @ChrisSchaller 直到任务完成才显示 HeavyWindow,这对我来说没问题,但我想显示启动画面。当 HeavyWindow 完成任务时,它会显示出来,所以我现在关闭了启动画面。 为什么要从另一个线程启动启动画面?这不是必需的,也没有多大意义。您应该在启动工作线程之前显示该窗口。为什么这不适合你。 【参考方案1】:

试试这个:

Window splash;
private void RunHeavyTask()

    ShowSplashScreen("Please wait...");
    //simulate "heavy task" by sleeping for 5 seconds
    Thread.Sleep(5000);
    //close the splash window
    splash.Dispatcher.BeginInvoke(() => splash.Close());


private void ShowSplashScreen(string str)

    Thread newWindowThread = new Thread(new ThreadStart(() =>
    
        SynchronizationContext.SetSynchronizationContext(
            new DispatcherSynchronizationContext(
                Dispatcher.CurrentDispatcher));

        //pass str to your custom Window here...
        splash = new Window()  Content = new ProgressBar()  IsIndeterminate = true  ;
        splash.Closed += (s, e) =>
           Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background);

        splash.Show();
        Dispatcher.Run();
    ));
    newWindowThread.SetApartmentState(ApartmentState.STA);
    newWindowThread.IsBackground = true;
    newWindowThread.Start();

当调用RunHeavyTask()的线程被阻塞时,它将在后台线程上显示一个带有不确定ProgressBar的窗口。

【讨论】:

感谢@mm8 的回复。这段代码与我已经拥有的几乎相同。 SetApartmentState(ApartmentState.STA) 阻塞另一个后台线程的问题,在我的情况下这是不可接受的。 @folibis:什么阻碍了什么?您的示例中也有ApartmentState.STA。这是所有窗口线程的硬性要求。 是的,这是个问题。我有几个在后台运行的线程。在使用SetApartmentState(ApartmentState.STA) 运行启动画面线程后,所有这些线程都被阻塞了。 @folibis:请用一个最小的例子证明你的观点。 我在上面的问题中提供了代码。那就是我现在使用的代码。【参考方案2】:

不清楚究竟你想做什么。更多细节会很好,因为您的一般方法似乎需要优化。

通常,您不会在单独的线程中启动 Window 以在 UI 线程上执行繁重的工作(CPU 限制)。而是您会在后台线程上执行 CPU 密集型任务。您可以异步等待此任务,然后在任务完成后关闭启动屏幕。

以下简单示例显示了用于在后台线程上异步执行 CPU 绑定工作以保持 UI 线程响应的模式,同时使用IProgress<T> 使用注册的委托从后台线程访问 UI 线程Progress<T> 的一个实例。 该示例假定初始屏幕显示了一些进度,并为此公开了一个 Progress 属性:

// Show a splash screen,
// while executing CPU bound work asynchronously on a background thread
private async Task RunHeavyTaskAsync()

  splashScreen = new Window();
  splashScreen.Show();
 
  // Use IProgress<T> to access the UI thread via a delegate,
  // which is registered using the constructor of Progress<T>
  var progressReporter = new Progress<double>(value => splash.Progress = value);

  // Pass the IProgress<T> instance to the background thread
  // and wait non-blocking (asynchronously) for the thread to complete
  await Task.Run(() => DoWork(progressReporter));

  // Close splash screen after 5s heavy work
  splashScreen.Close();


// Will be executed on a background thread
private void DoWork(IProgress<double> progressReporter)

  // Post progress from background thread (current context)
  // to the UI thread (splash screen).
  // IProgress<T>.Report() will execute the previously registered delegate
  progressReporter.Report(50);

  // Do 5s heavy work
  Thread.Sleep(5000);

  progressReporter.Report(100);

【讨论】:

以上是关于在 WPF 的另一个对话框中运行繁重任务时运行启动对话框的主要内容,如果未能解决你的问题,请参考以下文章

关闭浏览器时如何在队列中执行非常繁重的任务(在后台运行)?

哪个具有更快的运行时性能:WPF 或 Winforms?

无论安装的 Lync 客户端版本如何,都从 C# WPF 桌面应用程序启动 Lync 对话

非托管 C++ 应用程序中性能更好的 WPF 对话框 [重复]

仅启动一个基于 MFC 对话框的应用程序实例

如何创建在任何用户登录系统时运行的计划任务