2009-03-16 19 views
6

Me he encontrado con una situación bastante incómoda en un proyecto en el trabajo. Necesitamos crear usuarios en 4 o 5 servicios diferentes, y lo hemos configurado de tal manera que si uno falla, todos fallan. Están encapsulados dentro de un bloque de alcance de transacción.Sincronización de una llamada asincrónica en C#

Uno de los servicios que necesitamos para agregar usuarios requiere el uso de telnet y algunos datos. Hay otras maneras de hacerlo (que cuestan dinero), pero por ahora eso es lo que tenemos que hacer. Agregar un usuario lleva aproximadamente 3 minutos. Vamos a trabajar para reducirlo significativamente como se puede imaginar, pero ese no es realmente el punto. Esta llamada es asíncrona y tiene que ser para que funcione correctamente. El punto clave es que solo puede haber un máximo de 10 conexiones al servicio.

Nuestro proyecto se está configurando para crear usuarios en un lote. Así que potencialmente 50 usuarios crearon a la vez. Esto presenta un problema cuando solo se pueden hacer 10 conexiones a través de telnet, y no es probable que un usuario procesado tarde mucho más que el servicio telnet. Ahora necesito sincronizar este proceso para que el resto no pueda continuar hasta que haya terminado.

Estamos utilizando devoluciones de llamada y delegados con llamadas asíncronas para implementar la funcionalidad. ¿Cuál sería la mejor manera de encapsular la parte asíncrona y no continuar hasta que se complete?

¿Deberíamos configurar un bucle que solo finaliza cuando finaliza la llamada? ¿Hay algo en la biblioteca de Threading que pueda ayudar? Nunca he trabajado con hilos antes, así que esta será la primera vez para mí. ¿Qué herramientas hay para ayudar con este problema?

EDIT:

Si utilizo el/EndInvoke patrón BeginInvoke, se Asynch llamadas dentro del primer delegado honor a la/final comenzará también?

Ejemplo:

public void dele1(string message) { 
    Console.Write(message); 
    delegate2 del2 = new delegate2; 
    del2(); 
    Console.Write("End of Delegate 2"); 
} 

public void dele2() { 
    // Long Processing 
    Console.Write("Delegate 2"); 
} 

public delegate void delegate1(String message); 
public delegate void delegate2(); 

delegate1 del1 = new delegate1(dele1); 
del1("Delegate 1").BeginInvoke; 
del1().EndInvoke; 
Console.Write("End of Delegate 1"); 

// Resultados esperados (Fin de invocación espera hasta que haya terminado Delegado 2):

Delegate 1 
End of Delegate 2 
Delegate 2 
End of Delegate 1 

// O (Fin de invocación sólo espera a delegado del 1 al terminar, pero no cualquier llamada de delegado interna):

Delegate 1 
End of Delegate 2 
End of Delegate 1 
Delegate 2 

Finalizará la espera de invocación hasta que el segundo delegado finalice el proceso als o? ¿O tendré que usar los patrones de invocación en todas las llamadas delegadas?

+0

No estoy seguro de su sintaxis para su ejemplo editado. ¿Puedes aclarar más qué está sucediendo con del1? – strager

Respuesta

7

De hecho, podría utilizar monitores, semáforos, o incluso podría esperar hasta que su (s) llamada (s) de método asíncrono estén hechas.

Pero puede obtenerlo gratis también. Si llama al EndInvoke() en un delegado que se inició anteriormente con BeginInvoke(), bloqueará hasta que finalice el trabajo asincrónico.

No estoy seguro si esto ayuda porque tiene que estar utilizando este patrón asincrónico. Si lo hace, obtendrá la ejecución Asincrónica (y la conversión de Asincrónica de nuevo a llamadas síncronas) de forma gratuita.

Consulte Calling Synchronous Methods Asynchronously on MSDN para obtener más información sobre este patrón.

Espero que esto ayude!

+0

¡Eso suena exactamente a lo que necesito! Gracias amigo. –

2

Me parece que desea una cola ... si hay una solicitud en curso, agregue a la cola (con un Monitor); cuando se completa una llamada, verifique la cola ...

0

Tiene algunas opciones. Aquí hay un par de ideas:

En primer lugar, podría usar un ManualResetEvent para "bloquear" el hilo principal hasta que todas sus operaciones de sincronización se completen. La idea es simplemente hacer que funcione la función de llamada principal, luego hacer event.WaitOne(), y la función es responsable de establecer el evento.

Alternativamente, podría intentar usar algo como un Semaphore. Está diseñado para ayudar en estas situaciones, ya que puede limitarlo a 10 ocurrencias simultáneas sin tener que preocuparse por tratar de manejarlo usted mismo. Este sería probablemente mi enfoque preferido.

0

Hay dos razones para el uso de hilos:

  • para aprovecharse de los recursos de CPU, acelerando así las cosas
  • para escribir varias máquinas de estados simultáneos métodos tan sencillos, lo que los hace más legible

La desventaja de usar hilos es:

  • es reall y difícil

Si el cuello de botella es una espera de 3 minutos en una máquina remota, puede valer la pena señalar que varios hilos no le proporcionarán ninguna ventaja de rendimiento. Podrías escribir todo el thiing de un solo hilo, manteniendo un conjunto de hasta diez objetos de "máquina de estado" y moverlos a través de las etapas de creación del usuario, todo desde un hilo, con poca diferencia en el rendimiento general.

Así que lo que quizás esté buscando es un buen diseño de código legible, haciendo que la operación de creación del usuario se convierta en una secuencia de llamadas que se leen como un único método (que sería si las ejecutara como un hilo).

Otra forma de hacerlo es a través de un método de iterador, es decir, que contiene yield return declaraciones, que también le ofrece las ventajas de la legibilidad de un solo método. ¡Debo tener linked to this three times en la última semana! Es el artículo de Jeffrey Richter sobre AsyncEnumerator.

2

IAsyncResult tiene una propiedad AsyncWaitHandle que le proporciona un identificador de espera (si hay uno disponible) que se señalará cuando la operación finalice.

Por lo tanto, puede usar WaitHandle.WaitAll (o .WaitAny) para realizar esperas no giratorias en muchos tiradores a la vez.

0

No tengo muy claro cómo está utilizando TransactionScope (por lo que puede estar fuera de base), pero puede crear Transacciones dependientes que pueden transferirse a los subprocesos de trabajo. Su transacción de nivel superior no se comprometerá hasta que todas las DependentTransactions se hayan comprometido con éxito y viceversa con la reversión. Encontré esto como una manera fácil de hacer todo o nada a través de subprocesos siempre que la operación que está invocando encapsule la funcionalidad de confirmación/restitución (IEnlistmentNotification o similar).

+0

Tiene razón, y si no estuviéramos limitados a las 10 conexiones máximas para el proceso final, estaría bien. Desafortunadamente, estamos limitados a 10 (de hecho, estamos autorizados a 2), por lo que es más fácil hacerlos todos en secuencia en lugar de mantener una cola. –

+0

Conexiones simultáneas limitadas ... siempre es un requisito divertido;) – jsw

Cuestiones relacionadas