简体   繁体   English

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

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

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 callback fires off another thread until all the work has been finished.会有2个线程异步运行一段时间,当他们的回调方法get被调用时,回调会触发另一个线程,直到所有工作完成。 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如果这是.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!");

This has the advantage of working when Thread.Join isn't an option (eg 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).这具有在不能选择Thread.Join时工作的优势(例如,如果您正在使用线程池),并且比使用等待句柄更好地缩放(因为WaitHandle.WaitAll最多有 64 个句柄,而且您也不要'不需要分配尽可能多的对象)。

Note that if you're using.Net 4 you can also use the Task Parallel Library which makes this kind of thing easier.请注意,如果您使用的是 .Net 4,您还可以使用 Task Parallel Library,这使得这种事情变得更容易。

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.既然你说这不是.Net 4.0,这里有一个可以在.Net 3.5中使用的CountdownEvent的简单版本。 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.我最初写它是因为我需要一个可以在 Mono 还不支持.Net 4 的时候在 Mono 中使用的 CountdownEvent。 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.在所有线程上简单调用Join 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 (eg 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:)理想情况下,如果您在 .NET 4 上,请尝试使用任务并行库 - 它使很多事情变得更容易:)

Use WaitHandles, each thread should have a WaitHandle, such as ManualResetEvent and when finished call Set() on the event.使用WaitHandles,每个线程都应该有一个WaitHandle,比如ManualResetEvent,完成后调用Set()事件。

The main method should use WaitHandle.WaitAll passing in the handles for each thread. 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());

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.如果任何线程将计数器减为零,则 Set() 一个 AutoResetEvent。 WaitOne() on the AutoResetEvent. AutoResetEvent 上的 WaitOne()。

Rgds, Martin Rgds,马丁

In simplest situation you can use Join在最简单的情况下,您可以使用 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();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM