WPF / C#不要阻止UI

我有一个现有的WPF应用程序,它有几个部分。 每个部分都是UserControl,它实现了一个接口。

该接口指定了两个方法: void LoadData([...])bool UnloadData()

这些方法由UI线程调用,因此如果耗时,我们需要在后台工作中完成我们的工作。

LoadData没有问题,因为我们可以异步更新UI。 问题出在UnloadData()上。

如果我们真的可以离开当前视图,这应该返回。

这是使用当前数据状态(已保存/已修改/无效)计算的:

  • 保存返回true,
  • 无效询问您是否要保留一些正确的数据或不保存而离开
  • 修改后告诉您可以取消更改(返回true),继续编辑(返回false),保存当前数据(返回true)

问题在于“修改 – >保存”。 这是一个耗时的方法,所以为了尊重应用程序的原理,我们应该在后台线程(带忙指示符)中运行它。

但是如果我们只是启动线程并转到下一部分,它将返回“true”到方法调用,我们将直接启动下一个视图。

在我的情况下,在保存本地数据之前加载下一个视图可能是个问题。

所以:

有没有办法在返回“true”之前等待后台线程完成,而不阻止UI?

 public bool UnloadData(){ if(...){ LaunchMyTimeConsumingMethodWithBackgroundWorker(); return true;//Only when my time consuming method ends } //[...] } 

重要的编辑也许我不清楚:我知道如何使用BackgroundWorker或TPL。 我的问题是父类(调用UnloadData()的类是一个我无法编辑的类(由于多种原因:它在另一个不会重新加载的DLL中,它已经与70多个userControl一起工作,所有这些都在单独的项目(dll),由reflection加载。

这不是我的选择,我觉得它不好,但我现在要处理它。 我一直在寻找方法让我的方法等待返回我的方法。 我不确定是否有可能。 但我正在寻找一种解决方法,它将为我节省数周的工作量。

好了,我很兴奋,因为我想我可能已经发现了一些东西……

因此,您要做的是:创建一个DispatcherFrame,将该帧推送到Dispatcher,然后在RunWorkerCompleted中将Frame的Continue设置为false。

这是到目前为止的代码:

 public void Function() { BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += TimeConsumingFunction; var frame = new DispatcherFrame(); worker.RunWorkerCompleted += (sender, args) => { frame.Continue = false; }; worker.RunWorkerAsync(); Dispatcher.PushFrame(frame); } private void TimeConsumingFunction(object sender, DoWorkEventArgs doWorkEventArgs) { Console.WriteLine("Entering"); for (int i = 0; i < 3; i++) { Thread.Sleep(1000); } Console.WriteLine("Exiting"); } private void ButtonBase_OnClick(object sender, RoutedEventArgs e) { Function(); Console.WriteLine("Returns"); } 

您应该实现bool类型的依赖项属性“IsBusy”,在启动BackgoundWorker之前设置为TRUE,然后在工作完成时设置为FALSE。

在UI上,您可以在处理过程中将要禁用的任何function绑定到该属性(例如,用于加载下一个视图的按钮等); 或者可能会显示“取消”按钮。

您不应该“等待”操作完成,您可以在另一个变量中检索结果,BackgroundWorker将设置:

  BackgroundWorker _bw; bool _returnValue = false; private void button_Click(object sender, RoutedEventArgs e) { // if starting the processing by clicking a button _bw = new BackgroundWorker(); IsBusy = true; _bw.DoWork += new DoWorkEventHandler(_bw_DoWork); _bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_bw_RunWorkerCompleted); _bw.RunWorkerAsync(); } void _bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { IsBusy = false; // retrieve the result of the operation in the _returnValue variable } void _bw_DoWork(object sender, DoWorkEventArgs e) { _returnValue = UnloadData(); } private bool UnloadData() { if (...) { LaunchTimeConsumingMethod(); return true; } else return false; //etc ... } public bool IsBusy { get { return (bool)GetValue(IsBusyProperty); } set { SetValue(IsBusyProperty, value); } } // Using a DependencyProperty as the backing store for IsBusy. This enables animation, styling, binding, etc... public static readonly DependencyProperty IsBusyProperty = DependencyProperty.Register( ... ) 

您可以尝试使用.NET 4.5的新“ 等待 ”function。

await关键字允许您等待完成Task对象,而不会阻止UI。

试试这个修改:

 public async bool UnloadData() { if(...) { await Task.Factory.StartNew(() => { LaunchMyTimeConsumingMethod(); }); return true;//Only when my time consuming method ends } //[...] } 

将UnloadData视为异步操作,让async / awaitfunction处理同步完成和需要异步完成的情况:

 public async Task UnloadData(){ if(...){ // The await keyword will segment your method execution and post the continuation in the UI thread // The Task.Factory.StartNew will run the time consuming method in the ThreadPool await Task.Factory.StartNew(()=>LaunchMyTimeConsumingMethodWithBackgroundWorker()); // The return statement is the continuation and will run in the UI thread after the consuming method is executed return true; } // If it came down this path, the execution is synchronous and is completely run in the UI thread return false; } private async void button_Click(object sender, RoutedEventArgs e) { // Put here your logic to prevent user interaction during the operation's execution. // Ex: this.mainPanel.IsEnabled = false; // Or: this.modalPanel.Visibility = Visible; // etc try { bool result = await this.UnloadData(); // Do whatever with the result } finally { // Reenable the user interaction // Ex: this.mainPanel.IsEnabled = true; } } 

编辑

如果你不能修改UnloadData,那么就在ThreadPool上执行它,因为@BTownTKD指出:

  private async void button_Click(object sender, RoutedEventArgs e) { // Put here your logic to prevent user interaction during the operation's execution. // Ex: this.mainPanel.IsEnabled = false; // Or: this.modalPanel.Visibility = Visible; // etc try { // The await keyword will segment your method execution and post the continuation in the UI thread // The Task.Factory.StartNew will run the time consuming method in the ThreadPool, whether it takes the long or the short path bool result = await The Task.Factory.StartNew(()=>this.UnloadData()); // Do whatever with the result } finally { // Reenable the user interaction // Ex: this.mainPanel.IsEnabled = true; } } 

如果您的框架版本是4.0,您可能应该使用TPL:

 var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); // this will work only if you're running this code from UI thread, for example, by clicking a button Task.Factory.StartNew(() => UnloadData()).ContinueWith(t => /*update ui using t.Result here*/, uiScheduler); 

希望这可以帮助。

您必须实现一个回调函数(RunWorkerCompleted),这在后台工作程序完成时调用。

在这里查看一个示例: http : //msdn.microsoft.com/en-us/library/cc221403(v = vs.95).aspx