Предполагая, что вы можете использовать .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, но не добавление их в список точек в фоновом потоке. После того, как мы создали данные, сообщите потоку пользовательского интерфейса, что мы закончили, и пусть он позаботится о добавлении точек в список.
Преимущество этого метода состоит в том, что он не разделяет никакое изменяемое состояние - только 1 поток получает доступ к коллекции точек. Он также имеет то преимущество, что не требует никаких блокировок или явной синхронизации.
У него есть еще одна важная характеристика: ваш поток пользовательского интерфейса не будет блокироваться. Как правило, это хорошо, вы не хотите, чтобы ваше приложение выглядело замороженным. Если блокирование потока пользовательского интерфейса является требованием, нам придется немного переработать это решение.