How to call a method in a user interface thread when using TPL?

I am working on an MVVM application that performs several tasks in the background using TPL. Tasks must report progress in the user interface so that the progress dialog can be updated. Because the application is MVVM, the progress dialog is tied to a property of the view model called Progress, which is updated using the view model method with the signature UpdateProgress(int increment) . Background tasks should call this method to report progress.

I use the method to update the property, because it allows each task to increase the Progress property by different amounts. So, if I have two tasks, and the first takes four times to the second, the first task calls UpdateProgress(4) , and the second task calls UpdateProgress(1) . Thus, progress reaches 80% upon completion of the first task and at 100% upon completion of the second task.

My question is very simple: how do I call the view model method from my background tasks? The code is below. Thank you for your help.


The tasks use Parallel.ForEach() , in code that looks like this:

 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."; } } 

The viewModel.IncrementProgressCounter(1) statement does not throw an exception, but does not go to the main thread. Tasks are called from MVVM ICommand objects in code that looks like this:

 public void Execute(object parameter) { ... // Background Task #2: Resequence files var secondTask = firstTask.ContinueWith(t => this.ResequenceFiles(fileList, progressDialogViewModel)); ... } 
+4
source share
2 answers

To call the marshall method in the main UI thread, you can use the Dispatcher InvokeMethod method. If you use an MVVM Framework such as Carliburn, it has abstractions over the dispatcher, so you can do almost the same thing using Execute.OnUIThread (Action).

Check out this Microsoft article on how to use the dispatcher.

+2
source

Assuming your ViewModel is built on a UI thread (i.e.: by view or in response to a View-related event), which almost always refers to IMO, you can add this to your constructor:

 // Add to class: TaskFactory uiFactory; public MyViewModel() { // Construct a TaskFactory that uses the UI thread context uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext()); } 

Then, when you receive your event, you can use it for its marshal:

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

Edit: I made a utility class. It is static, but if you want, you can create an interface for it and make it non-static:

 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 dispatcher and executed asynchronously. /// <para>For additional operations on the UI thread, you can get a /// reference to the UI thread 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."); } } 

Using:

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

Source: https://habr.com/ru/post/917098/


All Articles