2011-10-10 14 views
6

Estoy escribiendo una aplicación, donde la mayor parte del trabajo se realiza por hilos de fondo (10 - 500 hilos).¿Cómo agregar funcionalidad de pausa/reanudar a una aplicación?

Me gustaría agregar la funcionalidad de pausa/reanudar.

Antes, podías hacer eso con Thread.Suspend y Thread.Resume. Pero esas funciones se consideran obsoletas ahora.

¿Hay algo que me permita hacer lo mismo con la misma facilidad?

estoy escribiendo el software en C#

+0

Debe proporcionar un poco más de información si desea algo así como una respuesta útil. ¿Qué están haciendo estos hilos? ¿Cómo comienzan? ¿En qué condiciones se deben "detener"? Sin más información, es imposible responder a esta pregunta. –

+2

500 hilos en un proceso es un * enorme * número de hilos, especialmente si muchos de ellos duermen la mayor parte del tiempo. Mi consejo es rediseñar su arquitectura para que no use más hilos que procesadores en la máquina. Los hilos son muy "pesados" en C#; deberías estar creando el mínimo absoluto necesario para hacer el trabajo. ¿Qué estás haciendo que estás creando tantos hilos? –

+0

Esta es una aplicación de rastreo web. Cada hilo pasa la mayor parte del tiempo esperando una respuesta de algún servidor web, todos los hilos se inician en el mismo lugar y deben pausarse cuando el usuario presiona el botón "pausa". La aplicación generalmente toma 700m-2G de memoria, y estoy de acuerdo con esto a menos que haya una mejor manera de hacerlo; incluso en 500 subprocesos, la aplicación generalmente requiere menos del 30% de una sola CPU para ejecutar –

Respuesta

2

haber escrito un rastreador de alto rendimiento en C#, lo que puedo decir con cierta autoridad que gestionan de forma explícita docenas o cientos de hilos no es el mejor camino a seguir. Se puede hacer (lo hice), pero es doloroso en extremo.

Dicho eso. . .

Si su aplicación está escrita la forma en que pienso, a continuación, cada hilo hace algo como esto:

while (!Shutdown) 
{ 
    // get next url to crawl from somewhere 
    // download the data from that url 
    // do something with the data 
} 

Pausa de los hilos entre descargas es bastante fácil. Sugeriría hacer dos instancias ManualResetEvent: una para continuar y otra para apagar. Estos son static de modo que todas las discusiones sobre orugas pueden acceder a ellos:

static ManualResetEvent ShutdownEvent = new ManualResetEvent(false); 
static ManualResetEvent ContinueEvent = new ManualResetEvent(true); 

A continuación, cada hilo utiliza WaitAny en un bucle:

WaitHandle[] handles = new WaitHandle[] { ShutdownEvent, ContinueEvent }; 
while (true) 
{ 
    int handle = WaitHandle.WaitAny(handles); // wait for one of the events 
    if (handle == -1 || handle >= handles.Length) 
    { 
     throw new ApplicationException(); 
    } 

    if (handles[handle] = ShutdownEvent) 
     break; // shutdown was signaled 

    if (handles[handle] == ContinueEvent) 
    { 
     // download the next page and do something with the data 
    } 
} 

Nota que cuando he definido la matriz handles, he especificado ShutdownEvent primero. El motivo es que si se señalan varios elementos, WaitAny devuelve el índice más bajo que corresponde a un objeto señalado. Si la matriz se llenó en el otro orden, entonces no podrá cerrar sin hacer una pausa primero.

Ahora, si desea que los hilos se cierren, llame al ShutdownEvent.Set. Y si desea que los hilos se detengan, llame al ContinueEvent.Reset Cuando desee que los hilos se reanuden, llame al ContinueEvent.Set.

Hacer una pausa en medio de una descarga es un poco más difícil. Es posible hacerlo, pero el problema es que si hace una pausa durante demasiado tiempo, el servidor podría perder tiempo. Y luego tendrá que reiniciar la descarga desde el principio o, si el servidor y su código lo admiten, reinicie la descarga desde el punto en que la dejó. Cualquiera de las dos opciones es bastante dolorosa, por lo que no sugeriría intentar detenerla en el medio de una descarga.

2

¿Qué hace su aplicación?

500 hilos son demasiados: eso es 1/2 GB de memoria comprometida solo para las pilas. Y luego tienes todo el cambio de contexto.

Está bien que desee deshacerse de las llamadas Suspend y Resume, pero le sugiero que primero eche un vistazo a su arquitectura. ¿Puede pasar a los métodos APM (BeginXXX/EndXXX)?

+0

En su mayoría, esta es una aplicación de red, que descarga información de la web.Debido a que pasa la mayor parte del tiempo esperando las respuestas, me parece que es una buena idea hacerlo con multihilo. Si conoce una mejor manera de lograrlo, hágamelo saber –

2

Como advertencia sobre lo que voy a decir: no está claro qué hace su aplicación; hay muchos métodos simples para usar subprocesos de grupo de subprocesos como TPL, trabajadores de fondo, etc.

Sin embargo, si tiene subprocesos que ha creado (no subprocesos) y desea que se comuniquen, utilice Monitor.Wait y Monitor. Pulso con una condición de bloqueo booleana.

por ejemplo:

bool _isPaused; 

void DoWork() 
{ 
     while (true) 
     { 
      lock (_locker) 
      { 
       while (_isPaused) Monitor.Wait(_locker); 

       // your worker code here 

      } 

     } 
} 
     // 
void UnPause() 
{ 
     lock (_locker) 
     { 
      _isPaused=false; 
      Monitor.PulseAll(_locker); 
     } 
} 
1
No

realmente. Suspender/Reanudar son realmente simples y funcionan bien hasta que bloqueen su aplicación, por ej. al suspender un hilo que tiene el administrador de memoria o el sistema de archivos bloqueado :(

El enfoque habitual, un poco más complejo, es encontrar un lugar en sus hilos donde pueda esperar. En su caso, supongo que la mayoría de los hilos normalmente están bloqueados en algunas llamadas IO y están 'suspendidos', por lo que un buen lugar para aplicar una 'suspensión' es inmediatamente después de la lectura para atrapar esos hilos donde la lectura vuelve.

Usted puede hacer la suspensión real comprobando un indicador booleano global 'isRunning' según lo sugerido por @Andrey y, si se necesita una suspensión, bloquee un evento ManualResetEvent. Para suspender, borre el evento y luego el indicador. Para reanudar, configure la bandera y luego el evento.

Si usar globales te produce náuseas, puedes pasarle al ctor una instancia común de alguna clase que contenga los métodos flag, event y 'suspend(),' resume() 'y' checkForSuspend() '.

Rgds, Martin

Cuestiones relacionadas