我有一个WPF应用程序启动3个线程,需要等待它们完成。我在这里阅读了许多处理此问题的帖子,但似乎没有解决线程代码调用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();
...
非常感谢有关如何改进此代码的任何想法。看起来这应该是一个非常普遍的问题,但我似乎无法在任何地方找到它。
答案 0 :(得分:8)
假设您可以使用.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<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;
}
}
这门课的消费很容易:
// 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线程,我们必须稍微重做这个解决方案。