我如何让我的方法等待所有线程完成

本文关键字:线程 等待 方法 我的 | 更新日期: 2023-09-27 18:03:02

我有一个方法,它是触发线程做一些工作。有两个线程在一段时间内异步运行,当它们的回调方法被调用时,回调会触发另一个线程,直到所有的工作都完成。我如何让我的方法等待所有这些线程完成并被触发?

我如何让我的方法等待所有线程完成

如果这是。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,你也可以使用任务并行库,它使这类事情变得更容易。

更新:

既然你说这不是。net 4.0,这里有一个可以在。net 3.5中使用的CountdownEvent的简单版本。我最初写它是因为当时Mono还不支持。net 4,我需要一个可以在Mono中使用的CountdownEvent。它不像真正的那样灵活,但它可以满足您的需要:

/// <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上,尝试使用任务并行库-它使很多事情变得更容易:)

联锁。在启动任何线程之前增加初始为零的计数器。联锁。在退出/环回之前减少每个线程中的计数器。如果任何线程将计数器减为零,则Set()一个AutoResetEvent。AutoResetEvent上的WaitOne()。

祝好,马丁

使用WaitHandle,每个线程都应该有一个WaitHandle,例如ManualResetEvent,当完成时调用事件的Set()

主方法应该使用WaitHandle。为每个线程传递句柄。

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

在最简单的情况下,您可以使用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();