Récemment, lors d'une interview, j'ai eu cette question.
Q: Avez-vous écrit des applications multithread?
R: Oui
Q: Voulez-vous expliquer plus?
R: J'ai utilisé Tasks
(bibliothèque de tâches parallèles) pour effectuer certaines tâches comme waiting for some info from internet while loading UI
. Cela améliore la convivialité de mon application.
Q: Mais, juste que vous avez utilisé TPL
signifie que vous avez écrit une application multithreaded
?
Moi: (Je ne sais pas quoi dire1)
Alors, quelle est exactement une application multi-thread? Est-ce différent de l'utilisation de Tasks
?
Les tâches peuvent être utilisées pour représenter des opérations se déroulant sur plusieurs threads, mais elles ne ont pour. Un peut écrire des applications TPL complexes qui ne s'exécutent que dans un seul thread. Lorsque vous avez une tâche qui, par exemple, représente une demande réseau pour certaines données, cette tâche est pas va créer des threads supplémentaires pour atteindre cet objectif. Un tel programme est (espérons-le) asynchrone, mais pas nécessairement mutlithread.
Le parallélisme fait plus d'une chose en même temps. Cela peut ou non être le résultat de plusieurs threads.
Allons avec une analogie ici.
Voici comment Bob cuisine le dîner:
Bob a cuisiné de manière entièrement synchrone sans multithreading, asynchronie ou parallélisme lors de la cuisson de son dîner.
Voici comment Jane cuisine le dîner:
Jane a utilisé la cuisine asynchrone (sans aucun multithreading) pour atteindre le parallélisme lors de la cuisson de son dîner.
Voici comment Servy prépare le dîner:
Servy a exploité plusieurs threads (travailleurs) qui ont chacun individuellement fait leur travail de manière synchrone, mais qui ont travaillé de manière asynchrone les uns par rapport aux autres pour parvenir au parallélisme.
Bien sûr, cela devient d'autant plus intéressant si nous considérons, par exemple, si notre poêle a deux brûleurs ou un seul. Si notre poêle a deux brûleurs, nos deux fils, Bob et Jane, sont tous les deux capables de faire leur travail sans se gêner beaucoup. Ils peuvent se cogner un peu les épaules, ou chacun essaie de saisir quelque chose de la même armoire de temps en temps, donc ils seront chacun ralentis de bit, mais pas beaucoup. Si chacun a besoin de partager un seul brûleur, alors il ne pourra pas faire grand-chose du tout lorsque l'autre personne travaillera. Dans ce cas, le travail ne se fera pas plus rapidement que d'avoir une seule personne à faire la cuisine de manière entièrement synchrone, comme Bob le fait quand il est seul. Dans ce cas, nous cuisinons avec plusieurs fils, mais notre cuisine n'est pas parallélisée. Tous les travaux multithreads ne sont pas en fait des travaux parallèles. C'est ce qui se produit lorsque vous exécutez plusieurs threads sur une machine avec un processeur. En fait, vous ne faites pas le travail plus rapidement que d'utiliser un seul fil, car chaque fil fait son tour à tour de rôle. (Cela ne signifie pas que les programmes multithreads sont inutiles sur les processeurs à un cœur, ils ne le sont pas, c'est juste que la raison de leur utilisation n'est pas d'améliorer la vitesse.)
Nous pouvons même envisager comment ces cuisiniers feraient leur travail en utilisant la bibliothèque parallèle de tâches, pour voir quelles utilisations du TPL correspondent à chacun de ces types de cuisiniers:
Donc, d'abord, nous avons bob, écrivant simplement du code non-TPL normal et faisant tout de manière synchrone:
public class Bob : ICook
{
public IMeal Cook()
{
Pasta pasta = PastaCookingOperations.MakePasta();
Sauce sauce = PastaCookingOperations.MakeSauce();
return PastaCookingOperations.Combine(pasta, sauce);
}
}
Ensuite, nous avons Jane, qui démarre deux opérations asynchrones différentes, puis les attend toutes les deux après avoir démarré chacune d'elles pour calculer son résultat.
public class Jane : ICook
{
public IMeal Cook()
{
Task<Pasta> pastaTask = PastaCookingOperations.MakePastaAsync();
Task<Sauce> sauceTask = PastaCookingOperations.MakeSauceAsync();
return PastaCookingOperations.Combine(pastaTask.Result, sauceTask.Result);
}
}
Pour rappel ici, Jane utilise le TPL, et elle fait une grande partie de son travail en parallèle, mais elle utilise uniquement n seul thread pour faire son travail.
Ensuite, nous avons Servy, qui utilise Task.Run
pour créer une tâche qui représente le travail dans un autre thread. Il démarre deux ouvriers différents, les fait chacun travailler de façon synchrone, puis attend que les deux ouvriers finissent.
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);
}
}
Un Task
est une promesse pour l'achèvement des travaux futurs. Lorsque vous l'utilisez, vous pouvez l'utiliser pour I/O based
travail, qui pas vous oblige à utiliser plusieurs threads pour l'exécution de code. Un bon exemple est l'utilisation de la fonctionnalité C # 5 de async/await
avec HttpClient
qui effectue les E/S basées sur le réseau.
Cependant, vous pouvez profiter de TPL
pour effectuer un travail multithread. Par exemple, lorsque vous utilisez Task.Run
ou Task.Factory.Startnew
pour démarrer une nouvelle tâche, le travail en arrière-plan est mis en file d'attente pour vous sur le ThreadPool
, que le TPL
résume pour nous, vous permettant d'utiliser plusieurs threads.
Un scénario courant pour l'utilisation de plusieurs threads est lorsque vous avez un travail lié au processeur qui peut être effectué simultanément (en parallèle). Travailler avec une application multithread implique une grande responsabilité.
Nous voyons donc que travailler avec TPL
ne signifie pas nécessairement utiliser plusieurs threads, mais vous pouvez certainement en tirer parti pour faire du multithreading.
Q: Mais, juste que vous avez utilisé TPL signifie que vous avez écrit une application multithread?
Question intelligente, tâche! = Multi-thread, en utilisant TaskCompletionSource vous pouvez créer un Task
qui peut s'exécuter en un seul thread (peut-être un thread d'interface utilisateur uniquement) lui-même.
Task
n'est qu'une abstraction sur une opération qui pourrait se terminer à l'avenir. Cela ne signifie pas que le code est multi-thread. Généralement, Task
implique le multi-threading, pas nécessairement toujours.
Et gardez à l'esprit qu'avec la connaissance de TPL
vous ne pouvez pas dire que vous connaissez le multi-threading. Il existe de nombreux concepts que vous devez couvrir.
et bien sûr aussi la bibliothèque parallèle de tâches.
Remarque: ce n'est pas la liste complète, ce ne sont que du haut de ma tête.
Pour le filetage seul, je suggère http://www.albahari.com/threading/
Pour les didacticiels vidéo, je suggère Pluralsight . C'est payé mais ça vaut le coût.
Dernier point mais non le moindre: oui bien sûr, c'est Stackoverflow .
Mes 5 cents: vous n'avez pas à engager explicitement les threads avec Task.Run
Ou Task.Factory.StartNew
Pour rendre votre application TPL multithread. Considère ceci:
async static Task TestAsync()
{
Func<Task> 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();
Le code après await
dans doAsync
est exécuté simultanément sur différents threads. De la même manière, la concurrence peut être introduite avec l'API async sockets, HttpClient
, Stream.ReadAsync
Ou toute autre chose qui utilise le pool de threads (y compris les threads de pool IOCP).
Au sens figuré, chaque application .NET est déjà multithread, car le Framework utilise largement ThreadPool
. Même une simple application console affichera plus d'un thread pour System.Diagnostics.Process.GetCurrentProcess().Threads.Count
. Votre intervieweur aurait dû vous demander si vous avez écrit du code simultané (ou parallèle).