【问题标题】:Running splash dialog while running heavy task in another dialog in WPF在 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 完成任务时,它会显示出来,所以我现在关闭了启动画面。
  • 为什么要从另一个线程启动启动画面?这不是必需的,也没有多大意义。您应该在启动工作线程之前显示该窗口。为什么这不适合你。

标签: c# wpf multithreading task


【解决方案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);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-09
    • 1970-01-01
    • 2018-03-29
    • 2011-04-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多