我创建了一个简单的方法,它应该包含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?
答案 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();
}
正在进行测试以确定它是否解决了这个问题。