使用EventWaitHandles返回的时间早于预期

时间:2015-11-16 17:54:48

标签: c# multithreading thread-safety locking

我创建了一个简单的方法,它应该包含100个项目的列表,并且异步处理它们(一次最多只能处理MAX_CONCURRENT个元素),并且只返回一次所有元素处理:

/// <summary>Generic method to perform an action or set of actions
///          in parallel on each item in a collection of items, returning
///          only when all actions have been completed.</summary>
/// <typeparam name="T">The element type</typeparam>
/// <param name="elements">A collection of elements, each of which to
///                        perform the action on.</param>
/// <param name="action">The action to perform on each element. The
///                      action should of course be thread safe.</param>
/// <param name="MAX_CONCURRENT">The maximum number of concurrent actions.</param>
public static void PerformActionsInParallel<T>(IEnumerable<T> elements, Action<T> action)
{
    // Semaphore limiting the number of parallel requests
    Semaphore limit = new Semaphore(MAX_CONCURRENT, MAX_CONCURRENT);
    // Count of the number of remaining threads to be completed
    int remaining = 0;
    // Signal to notify the main thread when a worker is done
    AutoResetEvent onComplete = new AutoResetEvent(false);

    foreach (T element in elements)
    {
        Interlocked.Increment(ref remaining);
        limit.WaitOne();
        new Thread(() =>
        {
            try
            {
                action(element);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error performing concurrent action: " + ex);
            }
            finally
            {
                Interlocked.Decrement(ref remaining);
                limit.Release();
                onComplete.Set();
            }
        }).Start();
    }
    // Wait for all requests to complete
    while (remaining > 0)
        onComplete.WaitOne(10); // Slightly better than Thread.Sleep(10)
}
    /* We include a timeout on the `WaitOne()` before checking `remaining` again
        * to protect against the rare case where the last outstanding thread
        * decrements 'remaining' and then signals completion *between* the main thread
        * checking 'remaining' and waiting for the next completion signal, which would
        * otherwise result in the main thread missing the last signal and locking forever. */

大多数情况下,此代码的行为完全符合预期,但在极少数情况下,我发现该方法在列表中的每个元素完成处理之前返回(即突破最后的while循环)。当只剩下少数元素时似乎总会发生 - 例如我将处理97个元素,然后方法返回,然后元素98-100完成。

有没有什么我做错了可能导致remaining计数在所有元素实际处理之前达到0?

1 个答案:

答案 0 :(得分:1)

这是一个经过修改的解决方案,该解决方案利用CountdownEvent信号来避免使用remaining整数,并避免使用不可靠的AutoResetEvent onComplete轮询它所带来的繁忙等待:

public static void PerformActionsInParallel<T>(IEnumerable<T> elements, Action<T> action)
{
    int threads = MaxConcurrent ?? DefaultMaxConcurrentRequests;
    // Ensure elements is only enumerated once.
    elements = elements as T[] ?? elements.ToArray();
    // Semaphore limiting the number of parallel requests
    Semaphore limit = new Semaphore(MAX_CONCURRENT, MAX_CONCURRENT);
    // Count of the number of remaining threads to be completed
    CountdownEvent remaining = new CountdownEvent(elements.Count());

    foreach (T element in elements)
    {
        limit.WaitOne();
        new Thread(() =>
        {
            try
            {
                action(element);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error performing concurrent action: " + ex);
            }
            finally
            {
                remaining.Signal();
                limit.Release();
            }
        }).Start();
    }
    // Wait for all requests to complete
    remaining.Wait();
}

正在进行测试以确定它是否解决了这个问题。