开发者

How do I have my method wait for all threads to finish?

开发者 https://www.devze.com 2023-03-22 18:46 出处:网络
I have a method that is firing off threads to do some work. There will be 2 threads running asynchronously for a period of time, and when their callback method get\'s called, the开发者_StackOverflow c

I have a method that is firing off threads to do some work. There will be 2 threads running asynchronously for a period of time, and when their callback method get's called, the开发者_StackOverflow callback fires off another thread until all the work has been finished. How do I make my method wait for all these threads to finish and be fired off?


If this is .Net 4.0, you can use a 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!");

This has the advantage of working when Thread.Join isn't an option (e.g. if you're using the thread pool), and scaling better than using wait handles (because WaitHandle.WaitAll has a maximum of 64 handles, and you also don't need to allocate as many objects).

Note that if you're using .Net 4 you can also use the Task Parallel Library which makes this kind of thing easier.

Update:

Since you said this isn't .Net 4.0, here's a simple version of the CountdownEvent that can be used in .Net 3.5. I originally wrote it because I needed a CountdownEvent I could use in Mono at a time that Mono didn't support .Net 4 yet. It's not as flexible as the real one but it does what you need:

/// <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);
    }
}


Simple call Join on all the threads. So if you've just got two thread variables:

thread1.Join();
thread2.Join();

Or if you've got a collection:

foreach (Thread thread in threads)
{
    thread.Join();
}

It doesn't matter what order the threads finish in; the code will only continue after all the threads have completed.

If you've got new threads being created all the time, however, this may not help much... you may need to have some collection (e.g. a queue) which is only accessed within a lock, and get each thread-spawning activity to add the new thread to a queue... then iterate (carefully!) until the queue is empty:

while (true)
{
    Thread nextThread;
    lock (collectionLock)
    {
        if (queue.Count == 0)
        {
            break;
        }
        nextThread = queue.Dequeue();
    }
    nextThread.Join();
}

Ideally though, try using the Task Parallel Library if you're on .NET 4 - it makes a lot of this stuff easier :)


Interlocked.Increment an initially zeroed counter just before starting any thread. Interlocked.Decrement a counter in every thread just before exit/loopback. If any thread decrements the counter to zero, Set() an AutoResetEvent. WaitOne() on the AutoResetEvent.

Rgds, Martin


Use WaitHandles, each thread should have a WaitHandle, such as ManualResetEvent and when finished call Set() on the event.

The main method should use WaitHandle.WaitAll passing in the handles for each thread.

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


In simplest situation you can use 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();
0

精彩评论

暂无评论...
验证码 换一张
取 消