2014-02-06 99 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

相關問題