2011-09-06 9 views
34

Estoy tratando de comprender mejor las opciones Async y Parallel que tengo en C#. En los fragmentos a continuación, he incluido los 5 enfoques con los que me encuentro más. Pero no estoy seguro de cuál elegir - o mejor aún, ¿qué criterios a considerar al elegir:C# Opciones asincrónicas para procesar una lista

Método 1: Tarea

(ver http://msdn.microsoft.com/en-us/library/dd321439.aspx)

Calling StartNew es funcionalmente equivalente para crear una tarea utilizando uno de sus constructores y luego llamar a Start para programarlo para su ejecución. Sin embargo, a menos que la creación y la programación deban separarse, StartNew es el enfoque recomendado tanto para la simplicidad como para el rendimiento.

método StartNew de TaskFactory debe ser el mecanismo preferido para la creación y la programación de las tareas de cálculo, pero para escenarios en los que la creación y la programación deben ser separados, los constructores pueden ser utilizados, y el método de inicio de la tarea pueden usarse entonces para programar la tarea para la ejecución en un momento posterior.

// using System.Threading.Tasks.Task.Factory 
void Do_1() 
{ 
    var _List = GetList(); 
    _List.ForEach(i => Task.Factory.StartNew(_ => { DoSomething(i); })); 
} 

Método 2: QueueUserWorkItem

(ver http://msdn.microsoft.com/en-us/library/system.threading.threadpool.getmaxthreads.aspx)

Puede poner en cola tantas solicitudes grupo de subprocesos como memoria del sistema permite. Si hay más solicitudes que subprocesos de grupo de subprocesos, las solicitudes adicionales permanecen en cola hasta que los subprocesos del grupo de subprocesos estén disponibles.

Puede colocar los datos requeridos por el método de la cola en los campos de instancia de la clase en la que se define el método, o puede utilizar la sobrecarga de QueueUserWorkItem (WaitCallback, objetos) que acepta un objeto que contiene los datos necesarios.

// using System.Threading.ThreadPool 
void Do_2() 
{ 
    var _List = GetList(); 
    var _Action = new WaitCallback((o) => { DoSomething(o); }); 
    _List.ForEach(x => ThreadPool.QueueUserWorkItem(_Action)); 
} 

Método 3: Parallel.Foreach

(ver: http://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel.foreach.aspx)

La clase paralelo proporciona datos basados ​​en bibliotecas reemplazos paralelas para operaciones comunes como para bucles, para cada bucle, y ejecución de un conjunto de instrucciones.

El cuerpo delegado se invoca una vez para cada elemento en la fuente enumerable. Se proporciona con el elemento actual como parámetro.

// using System.Threading.Tasks.Parallel 
void Do_3() 
{ 
    var _List = GetList(); 
    var _Action = new Action<object>((o) => { DoSomething(o); }); 
    Parallel.ForEach(_List, _Action); 
} 

Método 4: IAsync.BeginInvoke

(ver: http://msdn.microsoft.com/en-us/library/cc190824.aspx)

BeginInvoke es asíncrona; por lo tanto, el control vuelve inmediatamente al objeto que llama después de que se invoca.

// using IAsync.BeginInvoke() 
void Do_4() 
{ 
    var _List = GetList(); 
    var _Action = new Action<object>((o) => { DoSomething(o); }); 
    _List.ForEach(x => _Action.BeginInvoke(x, null, null)); 
} 

Método 5: BackgroundWorker

(ver: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx)

Para configurar para una operación de fondo, añadir un controlador de eventos para el evento DoWork. Llame a su operación que consume mucho tiempo en este controlador de eventos. Para iniciar la operación, llame a RunWorkerAsync. Para recibir notificaciones de actualizaciones de progreso, gestione el evento ProgressChanged. Para recibir una notificación cuando se complete la operación, maneje el evento RunWorkerCompleted.

// using System.ComponentModel.BackgroundWorker 
void Do_5() 
{ 
    var _List = GetList(); 
    using (BackgroundWorker _Worker = new BackgroundWorker()) 
    { 
     _Worker.DoWork += (s, arg) => 
     { 
      arg.Result = arg.Argument; 
      DoSomething(arg.Argument); 
     }; 
     _Worker.RunWorkerCompleted += (s, arg) => 
     { 
      _List.Remove(arg.Result); 
      if (_List.Any()) 
       _Worker.RunWorkerAsync(_List[0]); 
     }; 
     if (_List.Any()) 
      _Worker.RunWorkerAsync(_List[0]); 
    } 
} 

supongo que el critieria obvias serían:

  1. es mejor que el otro para el rendimiento?
  2. ¿Hay alguna mejor que la otra para el manejo de errores?
  3. ¿Hay alguna mejor que la otra para monitoreo/retroalimentación?

Pero, ¿cómo usted elige? Gracias de antemano por sus ideas.

+1

También puede consultar System.Reactive (extensiones reactivas o rx.net). – lbergnehr

+0

¡Y ni siquiera tocó el CTP Async! :-) – xanatos

+0

Eso es cierto, pero me estaba apegando a C# 4. Buen punto, sin embargo. –

Respuesta

4

Sus primeros, tercer y cuarto ejemplos utilizan el ThreadPool implícitamente porque por tareas predeterminada se programan en el ThreadPool y las extensiones TPL utilizan ThreadPool así, la API simplemente oculta parte de la complejidad ver here y here. BackgroundWorkers son parte del espacio de nombres ComponentModel porque están destinados para su uso en escenarios de IU.

-2

El último es el mejor para 2,3 al menos. Tiene métodos/propiedades integradas para esto. Otras variantes son casi las mismas, solo diferentes versiones/envoltorios convenient

+0

En su defensa, hemos utilizado BackgroundWorker para realizar operaciones asincrónicas simples en la mayoría de nuestro código. Nos ha funcionado para muchas versiones del framework. Sin embargo, el enfoque en Do_5() nunca se ejecuta en Paralelo, solo Async. Estos otros enfoques tienen instrumentos MUY similares, por cierto. La razón por la que elegiría el BackgroundWorker hoy sería familiaridad. No ayuda a que mi código funcione mejor porque su andamio parece pesado. –

2

Reactive extensions es otra biblioteca próxima para manejar la programación asincrónica, especialmente cuando se trata de la composición de eventos y métodos asincrónicos.

No es nativo, sin embargo, ha sido desarrollado por Ms Labs. Está disponible tanto para .NET 3.5 como para .NET 4.0 y es esencialmente una colección de métodos de extensión en la interfaz IObservable<T> de .NET 4.0.

Hay muchos ejemplos y tutoriales en su sitio principal, y recomiendo consultar algunos de ellos. El patrón puede parecer un poco extraño al principio (al menos para los programadores de .NET), pero vale la pena, incluso si solo se trata de captar el nuevo concepto.

La verdadera fortaleza de las extensiones reactivas (Rx.NET) es cuando necesita componer varias fuentes y eventos asincrónicos. Todos los operadores están diseñados con esto en mente y manejan las partes feas de la asincronía por usted.

sitio principal: http://msdn.microsoft.com/en-us/data/gg577609

Guía para principiantes: http://msdn.microsoft.com/en-us/data/gg577611

Ejemplos:. http://rxwiki.wikidot.com/101samples

Dicho esto, el mejor patrón asincrónico probablemente depende de en qué situación estás en Algunos son mejores (más simple) para cosas más simples y algunos son más extensibles y fáciles de manejar cuando se trata de escenarios más complejos. Sin embargo, no puedo hablar por todos los que estás mencionando.

+4

Esta respuesta necesita mucho contexto. No es hasta el tercer párrafo que mencionas de qué "es" de lo que estás hablando. –

+0

Reparado. Perdón por eso, comenzó como una respuesta a un comentario y salió fuera de contexto. – lbergnehr

15

va a tomar estos en un orden arbitrario:

BackgroundWorker (#5)
me gusta usar BackgroundWorker cuando estoy haciendo las cosas con una interfaz de usuario. La ventaja que tiene es que los eventos de progreso y finalización se activan en el subproceso de UI, lo que significa que no obtienes desagradables excepciones cuando intentas cambiar los elementos de la interfaz de usuario. También tiene una buena forma incorporada de informar el progreso. Una desventaja que tiene este modo es que si tiene llamadas de bloqueo (como solicitudes web) en su trabajo, tendrá un hilo sin hacer nada mientras el trabajo está sucediendo. Sin embargo, esto probablemente no sea un problema si solo crees que tendrás un puñado de ellos.

IAsyncResult/Begin/End (APM, #4)
Este es un modelo generalizado y poderoso, pero difícil de usar. El manejo de errores es problemático ya que es necesario volver a capturar las excepciones en la llamada final, y las excepciones no detectadas no necesariamente regresarán a ninguna pieza relevante de código que pueda manejarlo. Esto tiene el peligro de suspender permanentemente las solicitudes en ASP.NET o simplemente tener errores que desaparecen misteriosamente en otras aplicaciones. También debe estar atento a la propiedad CompletedSynchronously. Si no realiza un seguimiento e informa de esto correctamente, el programa puede bloquearse y perder recursos. La otra cara de esto es que si se está ejecutando dentro del contexto de otro APM, debe asegurarse de que los métodos asíncronos a los que llama también informen este valor. Eso significa hacer otra llamada APM o usar un Task y enviarlo a un IAsyncResult para obtener su propiedad CompletedSynchronously.

También hay un montón de sobrecarga en las firmas: debe admitir un objeto arbitrario para pasar, realizar su propia implementación IAsyncResult si está escribiendo un método asíncrono que admite sondeos y manejadores de espera (incluso si está solo usando la devolución de llamada). Por cierto, solo deberías usar la devolución de llamada aquí. Cuando utiliza el identificador de espera o el sondeo IsCompleted, está perdiendo un hilo mientras la operación está pendiente.

Event-based Asynchronous Pattern (EAP)
Uno que no estaba en su lista, pero voy a mencionar en aras de la exhaustividad. Es un poco más amigable que el APM. Hay eventos en lugar de devoluciones de llamadas y hay menos basura colgando de las firmas de métodos. El manejo de errores es un poco más fácil ya que se guarda y está disponible en la devolución de llamada en lugar de volver a lanzarse. CompletedSynchronously tampoco forma parte de la API.

Tasks (#1)
tareas son otra API asíncrona de usar. El manejo de errores es sencillo: la excepción siempre está ahí para la inspección en la devolución de llamada y nadie se preocupa por CompletedSynchronously. Puede hacer dependencias y es una gran manera de manejar la ejecución de múltiples tareas asíncronas. Incluso puedes ajustar los métodos asíncronos APM o EAP (un tipo que te faltó). Otra cosa buena acerca del uso de tareas es que a su código no le importa cómo se implementa la operación. Puede bloquearse en un hilo o ser totalmente asincrónico, pero el código de consumo no se preocupa por esto. También puede mezclar operaciones de APM y EAP fácilmente con Tareas.

métodos Parallel.For (# 3)
Estos son ayudantes adicionales en la parte superior de Tareas. Pueden hacer parte del trabajo para crear tareas para usted y hacer que su código sea más legible, si sus tareas asíncronas son adecuadas para ejecutarse en un ciclo.

ThreadPool.QueueUserWorkItem (# 2)
Esta es una utilidad de bajo nivel que realmente se utiliza por ASP.NET para todas las solicitudes. No tiene ningún control de errores incorporado como las tareas, por lo que debe capturar todo y canalizarlo de nuevo a su aplicación si desea conocerlo. Es adecuado para trabajos intensivos en CPU pero no desea realizar ninguna llamada de bloqueo, como una solicitud web síncrona. Eso es porque mientras se ejecuta, está usando un hilo.

async/await Keywords
nuevo en .NET 4.5, estas palabras clave permiten escribir código asíncrono sin devoluciones de llamadas explícitas. Puede esperar en un Task y cualquier código debajo de él esperará a que la operación asincrónica se complete, sin consumir un hilo.

+0

Creo que BackgroundWorker (# 5) cubre EAP. Pero, ciertamente, gracias por sus ideas sobre la pregunta. –

+0

BackgroundWorker implementa EAP, pero no lo "cubre" por completo. Con un BackgroundWorker debe tener un método DoWork sincrónico, pero podría hacer una llamada EAP donde maneje las cosas de manera diferente. Podría iniciar otra operación asíncrona, como una llamada web, luego regresar a otro hilo y desencadenar el evento. Es ciertas circunstancias que pueden ser más deseables que un BackgroundWorker. – RandomEngy

Cuestiones relacionadas