How to properly wait for multiple threads that call Dispatcher.Invoke to complete in a WPF application

I have a WPF application that starts 3 threads and needs to wait for them to complete. I read a lot of posts here that deal with this, but no one seems to address the situation where the thread code calls Dispatcher.Invoke or Dispatcher.BeginInvoke. If I use the Join () method of the stream or ManualResetEvent, the stream blocks the Invoke call. Here's a simplified code snippet of an ugly solution that seems to work:

class PointCloud { private Point3DCollection points = new Point3DCollection(1000); private volatile bool[] tDone = { false, false, false }; private static readonly object _locker = new object(); public ModelVisual3D BuildPointCloud() { ... Thread t1 = new Thread(() => AddPoints(0, 0, 192)); Thread t2 = new Thread(() => AddPoints(1, 193, 384)); Thread t3 = new Thread(() => AddPoints(2, 385, 576)); t1.Start(); t2.Start(); t3.Start(); while (!tDone[0] || !tDone[1] || !tDone[2]) { Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { })); Thread.Sleep(1); } ... } private void AddPoints(int scanNum, int x, int y) { for (int i = 0; i < x; i++) { for (int j = 0; j < y; j++) { z = FindZ(x, y); if (z == GOOD_VALUE) { Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate() { Point3D newPoint = new Point3D(x, y, z); lock (_locker) { points.Add(newPoint); } } ); } } } tDone[scanNum] = true; } } from the main WPF thread... PointCloud pc = new PointCloud(); ModelVisual3D = pc.BuildPointCloud(); ... 

Any ideas on how to improve this code would be greatly appreciated. It seems like this should be a very common problem, but I cannot find it properly in circulation.

+4
source share
1 answer

Assuming you can use .NET 4, I will show you how to do this in a much cleaner way that avoids changing the volatile state across threads (and thus avoids blocking).

 class PointCloud { public Point3DCollection Points { get; private set; } public event EventHandler AllThreadsCompleted; public PointCloud() { this.Points = new Point3DCollection(1000); var task1 = Task.Factory.StartNew(() => AddPoints(0, 0, 192)); var task2 = Task.Factory.StartNew(() => AddPoints(1, 193, 384)); var task3 = Task.Factory.StartNew(() => AddPoints(2, 385, 576)); Task.Factory.ContinueWhenAll( new[] { task1, task2, task3 }, OnAllTasksCompleted, // Call this method when all tasks finish. CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()); // Finish on UI thread. } private void OnAllTasksCompleted(Task<List<Point3D>>[] completedTasks) { // Now that we've got our points, add them to our collection. foreach (var task in completedTasks) { task.Result.ForEach(point => this.points.Add(point)); } // Raise the AllThreadsCompleted event. if (AllThreadsCompleted != null) { AllThreadsCompleted(this, EventArgs.Empty); } } private List<Point3D> AddPoints(int scanNum, int x, int y) { const int goodValue = 42; var result = new List<Point3D>(500); var points = from pointX in Enumerable.Range(0, x) from pointY in Enumerable.Range(0, y) let pointZ = FindZ(pointX, pointY) where pointZ == goodValue select new Point3D(pointX, pointX, pointZ); result.AddRange(points); return result; } } 

Consuming this class is easy:

 // On main WPF UI thread: var cloud = new PointCloud(); cloud.AllThreadsCompleted += (sender, e) => MessageBox.Show("all threads done! There are " + cloud.Points.Count.ToString() + " points!"); 

Explanation of this method

Think of streaming differently: instead of trying to synchronize access to streams with shared data (like your list of points), do the hard work in the background thread instead, but don't mutate any general state (like don't add nothing to the list of points). For us, this means that we iterate over X and Y and find Z, but do not add them to the list of points in the background thread. Once we create the data, let the UI thread know that we are done and let it take care of adding items to the list.

This method has the advantage that it does not use any mutable state - only 1 thread accesses a collection of points. This also has the advantage of not requiring any locks or explicit synchronization.

This has another important characteristic: your UI thread will not block. This is usually good; you do not want your application to look frozen. If user interface thread blocking is a requirement, we will have to rework this solution a bit.

+8
source

All Articles