waitone example ejemplo c# .net multithreading semaphore

example - mutex c#



¿Semáforo inverso.NET? (8)

Tal vez sea demasiado tarde en la noche, pero no puedo pensar en una buena manera de hacer esto.

He iniciado un montón de descargas asíncronas, y quiero esperar hasta que todas se completen antes de que finalice el programa. Esto me lleva a creer que debo incrementar algo cuando comienza una descarga y disminuirlo cuando termina. Pero entonces, ¿cómo espero hasta que el conteo vuelva a ser 0 ?

Los semáforos funcionan de manera opuesta, ya que se bloquea cuando no hay recursos disponibles, no cuando todos están disponibles (los bloques cuando el recuento es 0, en lugar de no cero).


Aquí está mi implementación C # 2.0 de 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(); } }


Basado en las sugerencias aquí, esto es lo que se me ocurrió. Además de esperar hasta que el conteo sea 0, se suspenderá si genera demasiados hilos (count> max). Advertencia: Esto no está completamente probado.

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 }


Bueno ... puede recuperar todos los contadores de semáforos en el hilo principal para bloquearlos cuando el recuento es 0, en lugar de cero .

REVISADO: Aquí asumí 3 cosas:

  • Mientras el programa se está ejecutando, un nuevo trabajo de descarga puede comenzar en cualquier momento.
  • Al salir del programa, no habrá más descargas nuevas que deban ser atendidas.
  • Al salir del programa, debe esperar a que todos los archivos finalicen la descarga.

Así que aquí está mi solución, revisada:

Inicializa el Semáforo con un contador lo suficientemente grande para que nunca alcances el máximo (podría ser simplemente 100 o solo 10, según tu situación):

var maxDownloads = 1000; _semaphore = new Semaphore(0, maxDownloads);

Luego, en cada descarga, comienza con WaitOne () antes de iniciar la descarga para que, en caso de que se cierre el programa, no se puedan iniciar las descargas.

if (_semaphore.WaitOne()) /* proceeds with downloads */ else /* we''re terminating */

Luego, al finalizar la descarga, suelte un contador (si hemos adquirido uno):

finally { _semaphore.Release(1); }

Y luego, en el evento "Salir", consuma todos los contadores en el Semáforo :

for (var i = 0; i < maxDownloads; i++) _semaphore.WaitOne(); // all downloads are finished by this point.

...



En .NET 4 hay un tipo especial para ese propósito CountdownEvent .

O puedes construir algo similar a ti mismo así:

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


Para cada subproceso inicia Interlock. Incrementa un contador. Y para cada devolución de llamada en el final del hilo, disminuirlo.

Luego haga un bucle con un Thread.Sleep (10) o algo así hasta que la cuenta llegue a cero.



Tuve un problema similar en el que necesitaba reiniciar un servidor después de algún evento, pero tuve que esperar a que terminaran todas las solicitudes abiertas antes de matarlo.

Usé la clase CountdownEvent en el inicio del servidor para inicializarlo con 1, y dentro de cada solicitud hago:

try { counter.AddCount(); //do request stuff } finally { counter.Signal(); }

Y al recibir el ResetEvent, señalo al contador una vez para eliminar el inicio 1, y espere a que las solicitudes en vivo señalen que han terminado.

void OnResetEvent() { counter.Signal(); counter.Wait(); ResetServer(); //counter.Reset(); //if you want to reset everything again. }

Básicamente, inicializa CountdownEvent con uno, de modo que se encuentre en un estado sin señalización, y con cada llamada AddCount está aumentando el contador, y con cada llamada Signal, la estamos disminuyendo, permaneciendo siempre por encima de 1. En el hilo de espera, su primera señal es una vez para disminuir el valor inicial de 1 a 0, y si no hay subprocesos en ejecución, Wail () dejará de bloquearse inmediatamente, pero si hay otros subprocesos que aún están en ejecución, el subproceso de espera esperará hasta que señalen. Tenga cuidado, una vez que el contador llegue a 0, todas las llamadas AddCount subsiguientes generarán una excepción, primero debe restablecer el contador.