C# 从其他窗口的构造函数更新UI不工作

C# 从其他窗口的构造函数更新UI不工作,c#,wpf,multithreading,user-interface,C#,Wpf,Multithreading,User Interface,我目前正在尝试实现一个启动屏幕。我以此为出发点 my App.xaml.cs中的OnStartup如下所示: protected override void OnStartup(StartupEventArgs e) { //initialize the splash screen and set it as the application main window splashScreen = new MySplashScreen(); this.MainWindow =

我目前正在尝试实现一个启动屏幕。我以此为出发点

my App.xaml.cs中的OnStartup如下所示:

protected override void OnStartup(StartupEventArgs e)
{
    //initialize the splash screen and set it as the application main window
    splashScreen = new MySplashScreen();
    this.MainWindow = splashScreen;
    splashScreen.Show();

    //in order to ensure the UI stays responsive, we need to
    //do the work on a different thread
    Task.Factory.StartNew(() =>
    {
        //we need to do the work in batches so that we can report progress
        for (int i = 1; i <= 100; i++)
        {
            //simulate a part of work being done
            System.Threading.Thread.Sleep(30);

            //because we're not on the UI thread, we need to use the Dispatcher
            //associated with the splash screen to update the progress bar
            splashScreen.Dispatcher.Invoke(() => splashScreen.Progress = i);
            splashScreen.Dispatcher.Invoke(() => splashScreen.MyText = i.ToString());
        }

        //once we're done we need to use the Dispatcher
        //to create and show the main window
        this.Dispatcher.Invoke(() =>
        {
            //initialize the main window, set it as the application main window
            //and close the splash screen
            var mainWindow = new MainWindow();
            this.MainWindow = mainWindow;
            mainWindow.Show();
            splashScreen.Close();
        });
    });
}
这不是预期的效果。只有在完全调用构造函数之后,才会在初始屏幕的文本框中更新文本“From main window”。在执行“做一些需要几秒钟的事情…”之前,这并不像预期的那样


我犯了什么错?这是否如我所想的那样可能?

当您使用
Dispatcher.Invoke调用构造函数时,
Dispatcher
已经在忙于创建
main窗口。然后在
main窗口的构造函数中
再次调用
Dispatcher
Dispatcher.Invoke
有效地将代理排入调度程序队列。第一个委托运行完成后,下一个委托(在本例中是来自
主窗口的构造函数内部的委托)将退出队列并执行(始终与给定的
DispatcherPriority
)。这就是为什么您必须等待构造函数完成,即第一个委托已完成

我强烈建议从.NET 4.5()开始使用推荐的进度报告方式。它的构造函数捕获当前的
SynchronizationContext
,并对其执行报告回调。由于
Progress
的实例是在UI线程上创建的,因此回调将在适当的线程上自动执行,因此不再需要
Dispatcher
。这会解决你的问题。此外,在异步上下文中使用时,进度报告也可以使用取消

我还建议使用
async/await
来控制流。目标是在UI线程上创建
MainWindow
的实例。
另外,请始终避免使用
Thread.Sleep
,因为它会阻塞线程。在这种情况下,UI线程将因此变得无响应和冻结。使用异步(非阻塞)
等待任务。改为延迟。根据经验,用
任务
替换对
线程
的所有引用,即任务并行库是首选方法()

我相应地重构了您的代码:

App.xaml.cs

private SplashScreen { get; set; }

protected override async void OnStartup(StartupEventArgs e)
{
  // Initialize the splash screen.
  // The first Window shown becomes automatically the Application.Current.MainWindow
  this.SplashScreen = new MySplashScreen();
  this.SplashScreen.Show();

  // Create a Progress<T> instance which automatically 
  // captures the current SynchronizationContext (UI thread)
  // which makes the Dispatcher obsolete for reporting the progress to the UI. 
  // Pass a report (UI update) callback to the Progress<T> constructor,
  // which will execute automatically on the UI thread.
  // Because of the generic parameter which is in this case of type ValueTuple (C# 7),
  // 'System.ValueTuple' is required to be referenced (use NuGet Package Manager to install). 
  // Alternatively replace the tuple with an arg class.
  var progressReporter = new Progress<(int Value, string Message)>(ReportProgress);

  // Wait asynchronously for the background task to complete
  await DoWorkAsync(progressReporter);

  // Override the Application.Current.MainWindow instance.
  this.MainWindow = new MainWindow();

  // Asynchronously wait until MainWindow is initialized
  // Pass the Progress<T> instance to the method,
  // so that MainWindow can report progress too
  await this.MainWindow.InitializeAsync(progressReporter);

  this.SplashScreen.Close();    
  this.MainWindow.Show();    
}

private async Task DoWorkAsync(IProgress<(int Value, string Message)> progressReporter)
{
  // In order to ensure the UI stays responsive, we need to
  // do the work on a different thread
  await Task.Run(
    async () =>
    {
      // We need to do the work in batches so that we can report progress
      for (int i = 1; i <= 100; i++)
      {
        // Simulate a part of work being done
        await Task.Delay(30);

        progressReporter.Report((i, i.ToString()));            
      }
    });
}

// The progress report callback which is automatically invoked on the UI thread.   
// Requires 'System.ValueTuple' to be referenced (see NuGet)
private void ReportProgress((int Value, string Message) progress)
{
  this.SplashScreen.Progress = progress.Value;
  this.SplashScreen.MyText = progress.Message;
}
public partial class MainWindow
{
  public MainWindow()
  {
    InitializeComponent();
  }


  public async Task InitializeAsync(IProgress<(int Value, string Message)> progressReporter)
  {
    await Task.Run(
      () =>
      {
        progressReporter.Report((100, "From MainWindow"));

        // Run the initialization routine that takes a few seconds   
      }
  }
}
private SplashScreen{get;set;}
启动时受保护的覆盖异步无效(StartupEventArgs e)
{
//初始化启动屏幕。
//显示的第一个窗口将自动成为Application.Current.Main窗口
this.SplashScreen=new MySplashScreen();
这个.SplashScreen.Show();
//创建一个进度实例,该实例将自动
//捕获当前SynchronizationContext(UI线程)
//这使得调度程序不再用于向UI报告进度。
//将报告(UI更新)回调传递给进度构造函数,
//它将在UI线程上自动执行。
//由于在本例中为ValueTuple(C#7)类型的泛型参数,
//需要引用“System.ValueTuple”(使用NuGet软件包管理器安装)。
//或者用arg类替换元组。
var progressReporter=新进度(ReportProgress);
//异步等待后台任务完成
等待DoWorkAsync(progressReporter);
//覆盖Application.Current.MainWindow实例。
this.MainWindow=新的MainWindow();
//异步等待,直到主窗口初始化
//将进度实例传递给方法,
//这样主窗口也可以报告进度
等待这个.MainWindow.InitializeAsync(progressReporter);
这个.SplashScreen.Close();
this.MainWindow.Show();
}
专用异步任务DoWorkAsync(IProgress progressReporter)
{
//为了确保UI保持响应性,我们需要
//在不同的线程上工作
等待任务。运行(
异步()=>
{
//我们需要分批完成这项工作,以便报告进展情况
对于(int i=1;i
{
progressReporter.Report((100,“从主窗口”);
//运行需要几秒钟的初始化例程
}
}
}

非常感谢您的回答和解释!这非常有效。
public partial class MainWindow
{
  public MainWindow()
  {
    InitializeComponent();
  }


  public async Task InitializeAsync(IProgress<(int Value, string Message)> progressReporter)
  {
    await Task.Run(
      () =>
      {
        progressReporter.Report((100, "From MainWindow"));

        // Run the initialization routine that takes a few seconds   
      }
  }
}