2011-09-06 60 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

相关问题