如何正确等待调用Dispatcher.Invoke的多个线程在WPF应用程序中完成

我有一个WPF应用程序启动3个线程,需要等待它们完成。 我在这里阅读了许多处理此问题的post,但似乎没有解决线程代码调用Dispatcher.Invoke或Dispatcher.BeginInvoke的情况。 如果我使用线程的Join()方法或ManualResetEvent,则线程会在Invoke调用上阻塞。 这是一个似乎有效的丑陋解决方案的简化代码片段:

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(); ... 

任何关于如何改进此代码的想法都将非常感激。 看起来这应该是一个非常普遍的问题,但我似乎无法在任何地方找到它。

假设您可以使用.NET 4,我将向您展示如何以更清晰的方式执行此操作,以避免跨线程共享可变状态(从而避免锁定)。

 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>[] 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 AddPoints(int scanNum, int x, int y) { const int goodValue = 42; var result = new List(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; } } 

消费这门课很容易:

 // 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!"); 

这种技术的解释

考虑以不同方式进行线程化:而不是尝试将线程访问同步到共享数据(例如,您的点列表),而是在后台线程上做大量工作,但不要改变任何共享状态(例如,不要向点列表添加任何内容) 。 对我们来说,这意味着循环遍历X和Y并找到Z,但不将它们添加到后台线程中的点列表中。 一旦我们创建了数据,让UI线程知道我们已经完成,并让他负责将点添加到列表中。

这种技术的优点是不共享任何可变状态 – 只有1个线程访问点集合。 它还具有不需要任何锁定或显式同步的优点。

它有另一个重要特征:你的UI线程不会阻塞。 这通常是一件好事,您不希望您的应用程序显示为冻结。 如果要求阻止UI线程,我们必须稍微重做这个解决方案。