2014-02-06 73 views
10

在将我的问题标记为重复项之前,请听我说。如何在长时间运行* UI *操作期间刷新UI

大多数人都有一个长时间运行的非UI操作,他们正在做,并需要解锁UI线程。我有一个长时间运行的UI操作,其中必须在阻塞我的应用程序的其余部分的UI线程上运行。基本上,我在运行时动态构建DependencyObject,并将它们添加到我的WPF应用程序的UI组件上。需要创建的DependencyObject的数量取决于用户输入,其中没有限制。我有一个测试输入有大约6000 DependencyObject需要创建并加载它们需要几分钟。

在这种情况下使用后台工作程序的常用解决方案不起作用,因为一旦DependencyObject由后台工作程序创建,它们就不能再添加到UI组件,因为它们是在后台线程上创建的。

我目前的解决方案尝试是在后台线程中运行循环,为每个工作单元分配UI线程,然后调用Thread.Yield()为UI线程提供更新机会。这几乎奏效 - UI线程在操作过程中确实有机会更新自己,但应用程序仍然被阻止。

如何让我的应用程序在这个长时间运行的操作过程中不断更新UI和处理其他窗体上的事件?

编辑: 按照要求,我目前的“解决方案”的一个例子:

private void InitializeForm(List<NonDependencyObject> myCollection) 
{ 
    Action<NonDependencyObject> doWork = (nonDepObj) => 
     { 
      var dependencyObject = CreateDependencyObject(nonDepObj); 
      UiComponent.Add(dependencyObject); 
      // Set up some binding on each dependencyObject and update progress bar 
      ... 
     }; 

    Action background =() => 
     { 
      foreach (var nonDependencyObject in myCollection) 
      { 
       if (nonDependencyObject.NeedsToBeAdded()) 
       { 
        Dispatcher.Invoke(doWork, nonDependencyObject); 
        Thread.Yield(); //Doesn't give UI enough time to update 
       } 
      } 
     }; 
    background.BeginInvoke(background.EndInvoke, null); 
} 

更改Thread.Yield()Thread.Sleep(1)似乎工作,但真正好的解决办法?

+0

发布您的代码的例子。 –

+0

看来,主要问题是您需要创建的DependencyObjects的数量。在这种情况下,您可以尝试将创建分解为您在调度程序中排队的多个任务。只要单个DependencyObject的创建不是太长时间运行,就可以以块的形式处理它们,并保持UI的相应响应。 “如果我们将计算任务分解为可管理的块,我们可以定期返回调度程序并处理事件,我们可以给WPF一个重绘和处理输入的机会。” - http://msdn.microsoft.com/en-us/library/ms741870(v=vs.110).aspx – DavidN

+1

'基本上,我在运行时动态构建DependencyObjects并将它们添加到我的WPF应用程序的UI组件' - 我一直在想如果你真的需要的是一个'ItemsControl'和一个合适的ViewModel。 –

回答

10

有时确实需要在UI线程上做后台工作,尤其是当大多数工作是处理用户输入时。

示例:实时语法突出显示,即用型。将这种后台操作的一些子工作项目卸载到一个池线程中是可能的,但这并不能消除编辑器控件的文本在每个新输入的字符上发生变化的事实。

帮助:await Dispatcher.Yield(DispatcherPriority.ApplicationIdle)。这将使用户输入事件(鼠标和键盘)成为WPF Dispatcher事件循环的最佳优先级。后台工作过程可能如下所示:

async Task DoUIThreadWorkAsync(CancellationToken token) 
{ 
    var i = 0; 

    while (true) 
    { 
     token.ThrowIfCancellationRequested(); 

     await Dispatcher.Yield(DispatcherPriority.ApplicationIdle); 

     // do the UI-related work 
     this.TextBlock.Text = "iteration " + i++; 
    } 
} 

这将使UI保持响应,并尽可能快地完成后台工作,但具有空闲优先级。

我们可能需要一些油门(等待迭代之间至少100毫秒)和更好的取消逻辑,以增强它:

async Task DoUIThreadWorkAsync(CancellationToken token) 
{ 
    Func<Task> idleYield = async() => 
     await Dispatcher.Yield(DispatcherPriority.ApplicationIdle); 

    var cancellationTcs = new TaskCompletionSource<bool>(); 
    using (token.Register(() => 
     cancellationTcs.SetCanceled(), useSynchronizationContext: true)) 
    { 
     var i = 0; 

     while (true) 
     { 
      await Task.Delay(100, token); 
      await Task.WhenAny(idleYield(), cancellationTcs.Task); 
      token.ThrowIfCancellationRequested(); 

      // do the UI-related work 
      this.TextBlock.Text = "iteration " + i++; 
     } 

    } 
} 

更新为OP发布了一个示例代码。

根据您发布的代码,我同意@ HighCore关于正确ViewModel的评论。

目前工作的方式,开始background.BeginInvoke后台操作在池中的线​​程,然后同步回调UI线程上紧foreach循环,用Dispatcher.Invoke。这只会增加额外的开销。此外,您并未观察此操作的结束,因为您完全忽略由background.BeginInvoke返回的IAsyncResult。因此,InitializeForm返回,而background.BeginInvoke继续后台线程。从本质上讲,这是一场随意而忘的呼叫。

如果你真的想坚持UI线程,下面是如何使用我描述的方法来完成。

请注意,_initializeTask = background()仍然是一个异步操作,尽管它发生在UI线程上。 如果没有在InitializeForm(由于UI重入性的影响,这将是一个非常糟糕的主意)内嵌套的Dispatcher事件循环,您将无法使其同步。

这就是说,一个简化版本(无油门或取消)可能是这样的:

Task _initializeTask; 

private void InitializeForm(List<NonDependencyObject> myCollection) 
{ 
    Action<NonDependencyObject> doWork = (nonDepObj) => 
     { 
      var dependencyObject = CreateDependencyObject(nonDepObj); 
      UiComponent.Add(dependencyObject); 
      // Set up some binding on each dependencyObject and update progress bar 
      ... 
     }; 

    Func<Task> background = async() => 
     { 
      foreach (var nonDependencyObject in myCollection) 
      { 
       if (nonDependencyObject.NeedsToBeAdded()) 
       { 
        doWork(nonDependencyObject); 
        await Dispatcher.Yield(DispatcherPriority.ApplicationIdle); 
       } 
      } 
     }; 

    _initializeTask = background(); 
} 
+1

感谢您的帮助。我明天早上上班时会尝试一下,如果它对我有用,请给出接受的答案。 – Taedrin

+0

我还应该提一下,我*希望*一个不容忽视的异步操作。尽管表单在后台初始化完成之前处于禁用状态,但我希望应用程序中的其他表单能够响应。 – Taedrin

+0

@ user1512185,所以你真的不在乎后台操作何时真正结束,并且所有的'dependencyObject'都被添加了?或者在课程中是否有例外?这就是“即忘即忘”。 – Noseratio

相关问题