【发布时间】:2011-07-22 13:12:36
【问题描述】:
我有一个方法可以触发线程来做一些工作。会有2个线程异步运行一段时间,当他们的回调方法get被调用时,回调会触发另一个线程,直到所有工作完成。如何让我的方法等待所有这些线程完成并被触发?
【问题讨论】:
标签: c# multithreading
我有一个方法可以触发线程来做一些工作。会有2个线程异步运行一段时间,当他们的回调方法get被调用时,回调会触发另一个线程,直到所有工作完成。如何让我的方法等待所有这些线程完成并被触发?
【问题讨论】:
标签: c# multithreading
如果这是 .Net 4.0,您可以使用 CountdownEvent
const int threads = 10;
using( CountdownEvent evt = new CountdownEvent(threads) )
{
for( int x = 0; x < threads; ++x )
{
ThreadPool.QueueUserWorkItem((state) =>
{
// Do work here
((CountdownEvent)state).Signal();
}, evt);
}
evt.Wait();
}
Console.WriteLine("Everyone finished!");
这具有在Thread.Join 不是一个选项时工作的优势(例如,如果您正在使用线程池),并且比使用等待句柄更好地扩展(因为WaitHandle.WaitAll 最多有 64 个句柄,并且您也不需要分配尽可能多的对象)。
请注意,如果您使用的是 .Net 4,您还可以使用 Task Parallel Library,这使得这种事情变得更容易。
更新:
既然你说这不是 .Net 4.0,这里有一个简单版本的 CountdownEvent,可以在 .Net 3.5 中使用。我最初编写它是因为我需要一个可以在 Mono 中使用的 CountdownEvent,而当时 Mono 还不支持 .Net 4。它不像真正的那样灵活,但它可以满足您的需求:
/// <summary>
/// Represents a synchronization primitive that is signaled when its count reaches zero.
/// </summary>
/// <remarks>
/// <para>
/// This class is similar to but less versatile than .Net 4's built-in CountdownEvent.
/// </para>
/// </remarks>
public sealed class CountdownEvent : IDisposable
{
private readonly ManualResetEvent _reachedZeroEvent = new ManualResetEvent(false);
private volatile int _count;
private volatile bool _disposed;
/// <summary>
/// Initializes a new instance of the <see cref="CountdownEvent"/> class.
/// </summary>
/// <param name="initialCount">The initial count.</param>
public CountdownEvent(int initialCount)
{
_count = initialCount;
}
// Disable volatile not treated as volatile warning.
#pragma warning disable 420
/// <summary>
/// Signals the event by decrementing the count by one.
/// </summary>
/// <returns><see langword="true" /> if the count reached zero and the event was signalled; otherwise, <see langword="false"/>.</returns>
public bool Signal()
{
CheckDisposed();
// This is not meant to prevent _count from dropping below zero (that can still happen due to race conditions),
// it's just a simple way to prevent the function from doing unnecessary work if the count has already reached zero.
if( _count <= 0 )
return true;
if( Interlocked.Decrement(ref _count) <= 0 )
{
_reachedZeroEvent.Set();
return true;
}
return false;
}
#pragma warning restore 420
/// <summary>
/// Blocks the calling thread until the <see cref="CountdownEvent"/> is set.
/// </summary>
public void Wait()
{
CheckDisposed();
_reachedZeroEvent.WaitOne();
}
/// <summary>
/// Blocks the calling thread until the <see cref="CountdownEvent"/> is set, using a <see cref="TimeSpan"/> to measure the timeout.
/// </summary>
/// <param name="timeout">The timeout to wait, or a <see cref="TimeSpan"/> representing -1 milliseconds to wait indefinitely.</param>
/// <returns><see langword="true"/> if the <see cref="CountdownEvent"/> was set; otherwise, <see langword="false"/>.</returns>
public bool Wait(TimeSpan timeout)
{
CheckDisposed();
return _reachedZeroEvent.WaitOne(timeout, false);
}
/// <summary>
/// Blocks the calling thread until the <see cref="CountdownEvent"/> is set, using a 32-bit signed integer to measure the timeout.
/// </summary>
/// <param name="millisecondsTimeout">The timeout to wait, or <see cref="Timeout.Infinite"/> (-1) to wait indefinitely.</param>
/// <returns><see langword="true"/> if the <see cref="CountdownEvent"/> was set; otherwise, <see langword="false"/>.</returns>
public bool Wait(int millisecondsTimeout)
{
CheckDisposed();
return _reachedZeroEvent.WaitOne(millisecondsTimeout, false);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if( !_disposed )
{
if( disposing )
((IDisposable)_reachedZeroEvent).Dispose();
_disposed = true;
}
}
private void CheckDisposed()
{
if( _disposed )
throw new ObjectDisposedException(typeof(CountdownEvent).FullName);
}
}
【讨论】:
在所有线程上简单调用Join。因此,如果您只有两个线程变量:
thread1.Join();
thread2.Join();
或者,如果您有收藏:
foreach (Thread thread in threads)
{
thread.Join();
}
线程完成的顺序无关紧要;只有在所有线程都完成后,代码才会继续。
但是,如果您一直在创建新线程,这可能无济于事......您可能需要一些只能在内部访问的集合(例如队列)一个锁,并让每个线程生成活动将新线程添加到队列中......然后迭代(小心!)直到队列为空:
while (true)
{
Thread nextThread;
lock (collectionLock)
{
if (queue.Count == 0)
{
break;
}
nextThread = queue.Dequeue();
}
nextThread.Join();
}
不过,如果您在 .NET 4 上,请尝试使用任务并行库 - 它让很多事情变得更容易:)
【讨论】:
CountdownEvent 的自定义实现更新了我的答案,这是我不久前为个人项目编写的,您可以在.Net 3.5 中使用。您可以轻松地将其与ThreadPool 一起使用。
Interlocked. 在启动任何线程之前增加一个初始为零的计数器。互锁。在退出/环回之前在每个线程中减少一个计数器。如果任何线程将计数器减为零,则 Set() 一个 AutoResetEvent。在 AutoResetEvent 上的 WaitOne()。
Rgds, 马丁
【讨论】:
使用WaitHandles,每个线程都应该有一个WaitHandle,如ManualResetEvent,完成后对事件调用Set()。
main 方法应该使用WaitHandle.WaitAll 传递每个线程的句柄。
IList<WaitHandle> waitHandles = new List<WaitHandle>();
var newThread = new Thread(new ParameterizedThreadStart((handle) =>
{
// thread stuff goes here
((ManualResetEvent)handle).Set();
}));
var manualResetEvent = new ManualResetEvent(false);
waitHandles.Add(manualResetEvent);
newThread.Start(manualResetEvent);
// create other threads similarly
// wait for all threads to complete - specify a timeout to prevent a deadlock if a thread fails to set the event
WaitHandle.WaitAll(waitHandles.ToArray());
【讨论】:
WaitAll 也有 64 个句柄限制,因此它的可扩展性不是很好。
在最简单的情况下,您可以使用 Join
Threading.Thread myThread1 = new Thread(new ThreadStart(Worker1));
Threading.Thread myThread2 = new Thread(new ThreadStart(Worker2));
myThread1.Start();
myThread2.Start();
myThread1.Join();
myThread2.Join();
【讨论】: