C# 从其他窗口的构造函数更新UI不工作
我目前正在尝试实现一个启动屏幕。我以此为出发点 my App.xaml.cs中的OnStartup如下所示: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 =
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
}
}
}