¿El uso de la biblioteca de tareas (TPL) hace que una aplicación sea multiproceso?

Recientemente, al ser entrevistado, recibí esta pregunta.

P: ¿Ha escrito aplicaciones multiproceso?

A: Sí

P: ¿Te importa explicar más?

R: Utilicé Tasks (biblioteca de tareas paralelas) para llevar a cabo algunas tareas, como waiting for some info from internet while loading UI . Esto mejora la usabilidad de mi aplicación.

P: Pero, ¿solo ha usado TPL significa que ha escrito una aplicación multithreaded ?

Yo: (No estoy seguro de qué decir1)

Entonces, ¿qué es exactamente una aplicación multiproceso? ¿Es diferente de usar Tasks ?

Las tareas se pueden usar para representar operaciones que tienen lugar en múltiples hilos, pero no es necesario. Uno puede escribir aplicaciones TPL complejas que solo se ejecutan en un solo hilo. Cuando tiene una tarea que, por ejemplo, representa una solicitud de red para algunos datos, esa tarea no creará hilos adicionales para lograr ese objective. Tal progtwig es (con suerte) asíncrono, pero no necesariamente mutlithreaded.

El paralelismo está haciendo más de una cosa al mismo tiempo. Esto puede ser o no el resultado de múltiples hilos.

Vamos con una analogía aquí.


Así es como Bob cocina la cena:

  1. Él llena una olla de agua y la hierve.
  2. Luego pone la pasta en el agua.
  3. Él drena la pasta cuando está hecha.
  4. Él prepara los ingredientes para su salsa.
  5. Él pone todos los ingredientes para su salsa en una olla.
  6. Él cocina su salsa.
  7. Él pone su salsa en su pasta.
  8. Él come la cena.

Bob ha cocinado sincrónicamente sin multihilo, asincronía o paralelismo al cocinar su cena.


Así es como Jane cocina la cena:

  1. Ella llena una olla de agua y comienza a hervirla.
  2. Ella prepara los ingredientes para su salsa.
  3. Ella pone la pasta en el agua hirviendo.
  4. Ella pone los ingredientes en la olla.
  5. Ella drena su pasta.
  6. Ella pone la salsa en su pasta.
  7. Ella come su cena.

Jane aprovechó la cocina asincrónica (sin ningún tipo de subprocesamiento múltiple) para lograr el paralelismo cuando cocinaba su cena.


Así es como Servy cocina la cena:

  1. Le dice a Bob que hierva una olla de agua, ponga la pasta cuando esté lista y que sirva la pasta.
  2. Le dice a Jane que prepare los ingredientes para la salsa, que la cocine y luego la sirve sobre la pasta cuando haya terminado.
  3. Él espera que Bob y Jane terminen.
  4. Él come su cena.

Servy aprovechó múltiples hilos (trabajadores) que hicieron su trabajo individualmente de forma síncrona, pero que trabajaron de forma asíncrona entre sí para lograr el paralelismo.

Por supuesto, esto se vuelve aún más interesante si consideramos, por ejemplo, si nuestra estufa tiene dos quemadores o solo uno. Si nuestra estufa tiene dos quemadores, entonces nuestros dos hilos, Bob y Jane, son capaces de hacer su trabajo sin involucrarse mutuamente, mucho. Podrían golpear un poco los hombros, o tratar de agarrar algo del mismo armario de vez en cuando, por lo que cada uno se ralentizará un poco , pero no mucho. Sin embargo, si cada uno necesita compartir un único quemador de la estufa, en realidad no podrá hacer mucho cuando la otra persona esté trabajando. En ese caso, el trabajo en realidad no se realizará más rápido que el simple hecho de que una sola persona cocine sincrónicamente, como hace Bob cuando está solo. En este caso, estamos cocinando con múltiples hilos, pero nuestra cocción no está paralelizada . No todo el trabajo multiproceso es en realidad trabajo paralelo . Esto es lo que sucede cuando se ejecutan varios hilos en una máquina con una CPU. En realidad, el trabajo no se realiza más rápido que el simple uso de un hilo, porque cada hilo se está turnando para hacer el trabajo. (Eso no significa que los progtwigs multiproceso no tienen sentido en las CPU de un núcleo, no lo son, es solo que la razón para usarlos no es para mejorar la velocidad).


Incluso podemos considerar cómo estos cocineros harían su trabajo utilizando la Biblioteca de tareas paralelas, para ver qué usos del TPL corresponden a cada uno de estos tipos de cocineros:

Así que primero tenemos bob, solo escribimos código normal que no es TPL y hacemos todo sincrónicamente:

 public class Bob : ICook { public IMeal Cook() { Pasta pasta = PastaCookingOperations.MakePasta(); Sauce sauce = PastaCookingOperations.MakeSauce(); return PastaCookingOperations.Combine(pasta, sauce); } } 

Luego tenemos a Jane, que inicia dos operaciones asíncronas diferentes, luego espera a ambas después de comenzar cada una de ellas para calcular su resultado.

 public class Jane : ICook { public IMeal Cook() { Task pastaTask = PastaCookingOperations.MakePastaAsync(); Task sauceTask = PastaCookingOperations.MakeSauceAsync(); return PastaCookingOperations.Combine(pastaTask.Result, sauceTask.Result); } } 

Como recordatorio aquí, Jane está usando el TPL, y está haciendo gran parte de su trabajo en paralelo, pero solo está usando un solo hilo para hacer su trabajo.

Luego tenemos a Servy, que usa Task.Run para crear una tarea que representa hacer trabajo en otro hilo . Él inicia dos trabajadores diferentes, hace que ambos trabajen sincrónicamente y luego espera a que los dos trabajadores terminen.

 public class Servy : ICook { public IMeal Cook() { var bobsWork = Task.Run(() => PastaCookingOperations.MakePasta()); var janesWork = Task.Run(() => PastaCookingOperations.MakeSauce()); return PastaCookingOperations.Combine(bobsWork.Result, janesWork.Result); } } 

Una Task es una promesa para que se complete el trabajo futuro. Al usarlo, puede usarlo para trabajos I/O based , que no requieren que use múltiples hilos para la ejecución del código. Un buen ejemplo es usar la función C # 5 de async/await con HttpClient que hace trabajo de E / S basado en red.

Sin embargo, puede aprovechar el TPL para realizar trabajos de subprocesos múltiples. Por ejemplo, al usar Task.Run o Task.Factory.Startnew para comenzar una nueva tarea, el trabajo detrás de escena se pone en cola para usted en el ThreadPool , que el TPL ThreadPool para nosotros, permitiéndole usar múltiples hilos.

Un escenario común para usar múltiples hilos es cuando tiene trabajo de límite de CPU que se puede hacer simultáneamente (en paralelo). Trabajar con una aplicación multiproceso viene con una gran responsabilidad.

Así que vemos que trabajar con el TPL no significa necaserly el uso de múltiples hilos, pero definitivamente se puede aprovechar para hacer subprocesos múltiples.

P: Pero, ¿solo ha usado TPL significa que ha escrito una aplicación multiproceso?

Pregunta inteligente, Tarea! = Multi-threaded, utilizando TaskCompletionSource puede crear una Task que se puede ejecutar en un solo hilo (puede ser sólo el hilo UI).

Task es solo una abstracción de una operación que puede completarse en el futuro. No significa que el código es multihebra. Por lo general, la Task involucra el multi-threading, no necesariamente siempre.

Y tenga en cuenta que solo con el conocimiento de TPL no puede decir que conoce el multi-threading. Hay muchos conceptos que necesitas cubrir.

  • Hilo
  • Primitivas de sincronización
  • Hilo de seguridad
  • Progtwigción asincrónica
  • Progtwigción en paralelo que es parte de TPL.
  • y mucho más …

y por supuesto también biblioteca paralela de tareas.

Nota: esta no es la lista completa, estos son solo parte de mi cabeza.


Recursos para aprender:

  • El viejo blog de Eric
  • El nuevo blog de Eric
  • Blog de Stephen toub

Para enhebrar solo, sugiero http://www.albahari.com/threading/

Para los tutoriales en video sugiero Pluralsight . Se paga pero vale la pena el costo.

Por último, pero no menos importante: sí, por supuesto, es Stackoverflow .

Mis 5 centavos: no tiene que Task.Run los hilos explícitamente con Task.Run o Task.Factory.StartNew para hacer que su aplicación TPL sea multiproceso. Considera esto:

 async static Task TestAsync() { Func doAsync = async () => { await Task.Delay(1).ConfigureAwait(false); Console.WriteLine(new { Thread.CurrentThread.ManagedThreadId }); }; var tasks = Enumerable.Range(0, 10).Select(i => doAsync()); await Task.WhenAll(tasks); } // ... TestAsync().Wait(); 

El código después de await dentro de doAsync se ejecuta al mismo tiempo en diferentes subprocesos. De manera similar, la concurrencia se puede introducir con los sockets asincrónicos API, HttpClient , Stream.ReadAsync o cualquier otra cosa que use el grupo de subprocesos (incluidos los subprocesos de grupo de IOCP).

Hablando en sentido ThreadPool , todas las aplicaciones .NET ya están multiproceso, porque el Framework usa ThreadPool extensivamente. Incluso una aplicación de consola simple mostrará más de un hilo para System.Diagnostics.Process.GetCurrentProcess().Threads.Count . Su entrevistador debería haberle preguntado si ha escrito un código concurrente (o paralelo).