2011-09-06 28 views
4

我正在使用TPL在後臺執行幾個任務的MVVM應用程序。這些任務需要向UI報告進度,以便可以更新進度對話框。由於該應用程序是MVVM,因此進度對話框將綁定到名爲Progress的視圖模型屬性,該屬性由具有簽名UpdateProgress(int increment)的視圖模型方法進行更新。後臺任務需要調用此方法來報告進度。如何在使用TPL時在UI線程上調用方法?

我使用一種方法來更新屬性,因爲它允許每個任務以不同的數量遞增Progress屬性。所以,如果我有兩個任務,第一個任務需要第二個任務的四倍,第一個任務調用UpdateProgress(4),第二個任務調用UpdateProgress(1)。因此,第一項任務完成時進度爲80%,第二項任務完成時爲100%。

我的問題真的很簡單:如何從我的後臺任務調用視圖模型方法?代碼如下。謝謝你的幫助。


的任務使用Parallel.ForEach(),代碼看起來像這樣:

private void ResequenceFiles(IEnumerable<string> fileList, ProgressDialogViewModel viewModel) 
{ 
    // Wrap token source in a Parallel Options object 
    var loopOptions = new ParallelOptions(); 
    loopOptions.CancellationToken = viewModel.TokenSource.Token; 

    // Process images in parallel 
    try 
    { 
     Parallel.ForEach(fileList, loopOptions, sourcePath => 
     { 
      var fileName = Path.GetFileName(sourcePath); 
      if (fileName == null) throw new ArgumentException("File list contains a bad file path."); 
      var destPath = Path.Combine(m_ViewModel.DestFolder, fileName); 
      SetImageTimeAttributes(sourcePath, destPath); 

      // This statement isn't working 
      viewModel.IncrementProgressCounter(1); 
     }); 
    } 
    catch (OperationCanceledException) 
    { 
     viewModel.ProgressMessage = "Image processing cancelled."; 
    } 
} 

聲明viewModel.IncrementProgressCounter(1)不是拋出一個異常,但它不是通過主線程獲得。該任務從MVVM ICommand對象調用,代碼如下所示:

public void Execute(object parameter) 
{ 
    ... 

    // Background Task #2: Resequence files 
    var secondTask = firstTask.ContinueWith(t => this.ResequenceFiles(fileList, progressDialogViewModel)); 

    ... 
} 

回答

2

馬歇爾方法調用,您可以使用Dispatcher的方法的InvokeMethod主UI線程。如果您使用像Carliburn這樣的MVVM框架,它對Dispatcher具有抽象,所以您可以使用Execute.OnUIThread(Action)執行幾乎相同的操作。

檢查this關於如何使用分派器的Microsoft文章。

9

假設你的視圖模型構建UI線程(即:經查看,或在響應於查看相關的事件),這是這種情況幾乎總是IMO,你可以將它添加到你的構造:

// Add to class: 
TaskFactory uiFactory; 

public MyViewModel() 
{ 
    // Construct a TaskFactory that uses the UI thread's context 
    uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext()); 
} 

然後,當你得到你的活動,你可以用它來名帥它:

void Something() 
{ 
    uiFactory.StartNew(() => DoSomething()); 
} 

編輯: 我做了一個UTIL類。它是靜態的,但如果你願意,你可以爲它創建一個接口,並使其非靜態:

public static class UiDispatcher 
{ 
    private static SynchronizationContext UiContext { get; set; } 

    /// <summary> 
    /// This method should be called once on the UI thread to ensure that 
    /// the <see cref="UiContext" /> property is initialized. 
    /// <para>In a Silverlight application, call this method in the 
    /// Application_Startup event handler, after the MainPage is constructed.</para> 
    /// <para>In WPF, call this method on the static App() constructor.</para> 
    /// </summary> 
    public static void Initialize() 
    { 
     if (UiContext == null) 
     { 
      UiContext = SynchronizationContext.Current; 
     } 
    } 

    /// <summary> 
    /// Invokes an action asynchronously on the UI thread. 
    /// </summary> 
    /// <param name="action">The action that must be executed.</param> 
    public static void InvokeAsync(Action action) 
    { 
     CheckInitialization(); 

     UiContext.Post(x => action(), null); 
    } 

    /// <summary> 
    /// Executes an action on the UI thread. If this method is called 
    /// from the UI thread, the action is executed immendiately. If the 
    /// method is called from another thread, the action will be enqueued 
    /// on the UI thread's dispatcher and executed asynchronously. 
    /// <para>For additional operations on the UI thread, you can get a 
    /// reference to the UI thread's context thanks to the property 
    /// <see cref="UiContext" /></para>. 
    /// </summary> 
    /// <param name="action">The action that will be executed on the UI 
    /// thread.</param> 
    public static void Invoke(Action action) 
    { 
     CheckInitialization(); 

     if (UiContext == SynchronizationContext.Current) 
     { 
      action(); 
     } 
     else 
     { 
      InvokeAsync(action); 
     } 
    } 

    private static void CheckInitialization() 
    { 
     if (UiContext == null) throw new InvalidOperationException("UiDispatcher is not initialized. Invoke Initialize() first."); 
    } 
} 

用法:

void Something() 
{ 
    UiDispatcher.Invoke(() => DoSomething()); 
} 
+0

這實際工作,我不得不說相當聰明。好的!我認爲它比Dispatcher.Invoke更優雅 – imgen

+0

在我的項目中,我有一個接口,感謝它我的代碼也是非常可測試的。 – Pellared

相關問題