Ответ 1
Ознакомьтесь с классом CountdownLatch в этой статье .
Обновление: теперь охватывается каркасом с версии 4.0, CountdownEvent class.
Возможно, это слишком поздно ночью, но я не могу придумать хороший способ сделать это.
Я начал кучу асинхронных загрузок, и я хочу подождать, пока они все не закончатся, прежде чем программа завершится. Это заставляет меня думать, что я должен что-то увеличивать, когда начинается загрузка, и уменьшать ее, когда она заканчивается. Но затем как подождать, пока счетчик будет 0 снова?
Семафоры вроде работают наоборот, когда вы блокируете, когда нет доступных ресурсов, а не когда они все доступны (блоки, когда count равно 0, а не ненулевое значение).
Ознакомьтесь с классом CountdownLatch в этой статье .
Обновление: теперь охватывается каркасом с версии 4.0, CountdownEvent class.
В .NET 4 для этой цели существует специальный тип CountdownEvent.
Или вы можете создать подобную вещь себе следующим образом:
const int workItemsCount = 10;
// Set remaining work items count to initial work items count
int remainingWorkItems = workItemsCount;
using (var countDownEvent = new ManualResetEvent(false))
{
for (int i = 0; i < workItemsCount; i++)
{
ThreadPool.QueueUserWorkItem(delegate
{
// Work item body
// At the end signal event
if (Interlocked.Decrement(ref remainingWorkItems) == 0)
countDownEvent.Set();
});
}
// Wait for all work items to complete
countDownEvent.WaitOne();
}
Похоже, System.Threading.WaitHandle.WaitAll может быть очень неплохо:
Ожидает, что все элементы в указанном массиве получат сигнал.
Ну... вы можете вырвать все счетчики семафора в основном потоке назад, чтобы блокировать, когда count равен 0, а не ненулевое.
ПЕРЕСМОТРЕННЫЙ: Здесь я принял 3 вещи:
Итак, вот мое решение, пересмотренное:
Инициализирует Семафор с достаточно большим счетчиком, поэтому вы никогда не нажимаете максимум (это может быть просто 100 или только 10 в зависимости от вашей ситуации):
var maxDownloads = 1000;
_semaphore = new Semaphore(0, maxDownloads);
Затем при каждой загрузке начинается с WaitOne() перед началом загрузки, так что в случае выхода из программы загрузка не может начаться.
if (_semaphore.WaitOne())
/* proceeds with downloads */
else
/* we're terminating */
Затем после завершения загрузки отпустите один счетчик (если мы его приобрели):
finally { _semaphore.Release(1); }
И затем в событии "Выход" расходуют все счетчики на Семафоре:
for (var i = 0; i < maxDownloads; i++)
_semaphore.WaitOne();
// all downloads are finished by this point.
...
У меня была аналогичная проблема, когда мне нужно было reset сервер на какое-то событие, но мне пришлось ждать, пока все открытые запросы закончатся, прежде чем убить его.
Я использовал класс CountdownEvent, когда сервер начинает инициализировать его с помощью 1 и внутри каждого запроса, который я делаю:
try
{
counter.AddCount();
//do request stuff
}
finally
{
counter.Signal();
}
И после получения ResetEvent я сигнализирую счетчик один раз, чтобы исключить запуск 1, и дождаться, пока запросы в реальном времени будут переданы.
void OnResetEvent()
{
counter.Signal();
counter.Wait();
ResetServer();
//counter.Reset(); //if you want to reset everything again.
}
В основном вы инициализируете CountdownEvent одним, чтобы он находился в состоянии без сигнализации, и с каждым вызовом AddCount вы увеличиваете счетчик, а с каждым сигнальным вызовом вы уменьшаете его, всегда оставаясь выше 1. В вашем потоке ожидания вы сначала сигнализируете об этом один раз, чтобы уменьшить начальное значение 1 до 0, и если нет потоков, работающих под управлением Wail(), немедленно прекратится блокировка, но если есть другие потоки, которые все еще работают, поток ожидания будет ждать, пока они не выдадут сигнал. Остерегайтесь, как только счетчик достигнет 0, все последующие вызовы AddCount вызовут исключение, вам нужно сначала reset счетчик.
Для каждого потока вы запускаете Interlock.Increment счетчик. И для каждого обратного вызова на финишную обработку, Decrement it.
Затем выполните цикл с Thread.Sleep(10) или что-то, пока счет не достигнет нуля.
Вот моя реализация С# 2.0 CountdownLatch:
public class CountdownLatch
{
private int m_count;
private EventWaitHandle m_waitHandle = new EventWaitHandle(true, EventResetMode.ManualReset);
public CountdownLatch()
{
}
public void Increment()
{
int count = Interlocked.Increment(ref m_count);
if (count == 1)
{
m_waitHandle.Reset();
}
}
public void Add(int value)
{
int count = Interlocked.Add(ref m_count, value);
if (count == value)
{
m_waitHandle.Reset();
}
}
public void Decrement()
{
int count = Interlocked.Decrement(ref m_count);
if (m_count == 0)
{
m_waitHandle.Set();
}
else if (count < 0)
{
throw new InvalidOperationException("Count must be greater than or equal to 0");
}
}
public void WaitUntilZero()
{
m_waitHandle.WaitOne();
}
}
Основываясь на предложениях здесь, это то, что я придумал. В дополнение к ожиданию, пока счет будет равен 0, он будет спать, если вы создадите слишком много потоков (count > max). Предупреждение: Это не полностью протестировано.
public class ThreadCounter
{
#region Variables
private int currentCount, maxCount;
private ManualResetEvent eqZeroEvent;
private object instanceLock = new object();
#endregion
#region Properties
public int CurrentCount
{
get
{
return currentCount;
}
set
{
lock (instanceLock)
{
currentCount = value;
AdjustZeroEvent();
AdjustMaxEvent();
}
}
}
public int MaxCount
{
get
{
return maxCount;
}
set
{
lock (instanceLock)
{
maxCount = value;
AdjustMaxEvent();
}
}
}
#endregion
#region Constructors
public ThreadCounter() : this(0) { }
public ThreadCounter(int initialCount) : this(initialCount, int.MaxValue) { }
public ThreadCounter(int initialCount, int maximumCount)
{
currentCount = initialCount;
maxCount = maximumCount;
eqZeroEvent = currentCount == 0 ? new ManualResetEvent(true) : new ManualResetEvent(false);
}
#endregion
#region Public Methods
public void Increment()
{
++CurrentCount;
}
public void Decrement()
{
--CurrentCount;
}
public void WaitUntilZero()
{
eqZeroEvent.WaitOne();
}
#endregion
#region Private Methods
private void AdjustZeroEvent()
{
if (currentCount == 0) eqZeroEvent.Set();
else eqZeroEvent.Reset();
}
private void AdjustMaxEvent()
{
if (currentCount <= maxCount) Monitor.Pulse(instanceLock);
else do { Monitor.Wait(instanceLock); } while (currentCount > maxCount);
}
#endregion
}