Est-Tâche.Usine.StartNew() de la garantie d'utiliser un autre thread que le thread appelant?
Je suis en train de créer une nouvelle tâche à partir d'une fonction, mais je ne voudrais pas qu'il fonctionne sur le même thread. Je n'aime pas le thread qui tourne sur de tant qu'il est un autre (de sorte que l'information donnée dans cette question n'aide pas).
Suis-je garanti que le code ci-dessous sera toujours la sortie TestLock
avant de permettre à Task t
de le saisir à nouveau? Si non, qu'est-ce que les recommandations de conception de motif pour empêcher la ré-entrency?
object TestLock = new object();
public void Test(bool stop = false) {
Task t;
lock (this.TestLock) {
if (stop) return;
t = Task.Factory.StartNew(() => { this.Test(stop: true); });
}
t.Wait();
}
Edit: Basé sur la ci-dessous la réponse de Jon Skeet et Stephen Toub, un moyen simple de façon déterministe empêcher la réentrée serait de passer un CancellationToken, comme l'illustre cette méthode d'extension:
public static Task StartNewOnDifferentThread(this TaskFactory taskFactory, Action action)
{
return taskFactory.StartNew(action: action, cancellationToken: new CancellationToken());
}
- Je doute que vous ne peut garantir qu'un nouveau fil de discussion sera créé lors de l'appel de
StartNew
. Une tâche est définie comme une opération asynchrone, ce qui n'implique pas nécessairement un nouveau thread. Peut utiliser un fil de discussion existant aussi quelque part, ou une autre façon de faire asynchrone. - Si vous êtes à l'aide de C# 5, envisager de remplacer
t.Wait()
avecawait t
.Wait
ne correspond vraiment pas à la philosophie de TPL. - Dans le code le plus efficace comportement serait si c' a utilisez le thread appelant. Ce thread est assis là à ne rien faire, après tout.
- Oui, c'est vrai, et je n'aurais pas l'esprit dans ce cas précis. Mais je préfère un comportement déterministe.
- Si vous utilisez un planificateur qui exécute uniquement les tâches sur un thread spécifique alors non, la tâche ne peut pas être exécuté sur un thread différent. Il est très courant d'utiliser un
SynchronizationContext
à assurez-vous que les tâches sont exécutées sur le thread de l'INTERFACE utilisateur. Si vous avez exécuté le code qui a appeléStartNew
sur le thread d'INTERFACE utilisateur comme ça, alors qu'ils seraient tous les deux sur le même thread. La seule garantie est que la tâche sera exécutée de manière asynchrone de laStartNew
appel (au moins si vous ne fournissez pas unRunSynchronously
drapeau. - Si vous souhaitez de "forcer" un nouveau thread pour être créé, utilisez la fonction " TaskCreationOptions.LongRunning' drapeau. par exemple:
Task.Factory.StartNew(() => { this.Test(stop: true); }, TaskCreationOptions.LongRunning);
Qui est une bonne idée si vos serrures pourrait mettre le fil dans un état d'attente pour une longue période de temps. - Erwin, si vous voulez vraiment fonctionner sur des threads séparés n'est-ce pas mieux fait de créer et d'utiliser des threads de façon explicite?
- La seule condition est de s'exécuter sur un autre thread, il serait exagéré que le pool de threads (en particulier via la bibliothèque de Tâches) est bien capable de gérer cela correctement.
- Pourquoi est - 'si(arrêt) return' vérifier à l'intérieur de la serrure ici? Que bool est local, pas besoin de protéger, sur lire (sûrement)
- L'idée était d'empêcher la sortie de la fonction (qui est juste un exemple d'action) jusqu'à ce que le verrou peut être acquise. Dans une situation réelle, vous seriez en droit de déplacer cette déclaration avant de la verrouiller.
- la solution a fonctionné pour moi 🙂
- LongRunningTask ne garantit PAS un nouveau fil, fournit un indicateur de TPL. Un nouveau fil de discussion peuvent être utilisés, mais ne sont pas garanties. Comme ilustrated par la réponse correcte, seulement CancellationToken n'est que
Vous devez vous connecter pour publier un commentaire.
J'ai envoyé Stephen Toub - un membre de la PFX Équipe - à propos de cette question. Il est venu vers moi très rapidement, avec beaucoup de détails, donc je vais juste copier et coller son texte ici. Je n'ai pas cité tous, comme la lecture d'une grande quantité de texte cité finit par obtenir moins à l'aise que la vanille noir sur blanc, mais vraiment, c'est Stephen - je ne sais pas ce beaucoup de choses 🙂 j'ai fait cette réponse wiki de la communauté pour refléter le fait que tous les bienfaits ci-dessous n'est pas vraiment mon contenu:
EDIT: Bon, il semble que j'ai été complètement faux, et un fil qui est actuellement en attente sur une tâche peut être détourné. Voici un exemple simple de ce qui se passe:
Exemple de sortie:
Comme vous pouvez le voir, il ya beaucoup de fois où le thread en attente est réutilisé pour exécuter cette nouvelle tâche. Cela peut se produire même si le thread a acquis un verrou. Méchant re-entrancy. Je suis suffisamment choqué et inquiet 🙁
Wait()
, non?Wait()
la tâche peut “détourner” le thread en cours, de sorte que le deuxième appel sera exécuté sur le même thread, avant que la première se termine. Mais depuis l'appel àWait()
est à l'extérieur de la serrure, vous avez la garantie que le deuxième appel ne s'exécutent sur le même thread alors que le premier appel détient toujours le verrou.Wait
sur un pool de threads thread pourrait inline l'appel au délégué de la tâche sur le thread courant. Si le thread qui appelleWait()
n'est pas un pool de threads thread alors l'invocation de la tâche n'aura pas lieu sur le même thread.Wait
:Task.Factory.StartNew(Launch, TaskCreationOptions.LongRunning).Wait()
ThreadStatic
champs, ou quelque chose comme ça.Wait
alors que vous possédez une serrure. Les verrous sont ré-entrant, donc si la tâche que vous êtes en attente essaie d'acquérir le verrou ainsi, il réussira - parce qu'il est déjà propriétaire du verrou, même si c'est logiquement dans une autre tâche. Que doit à l'impasse, mais il ne sera pas... quand il est ré-entrant. Fondamentalement, re-entrancy me rend nerveux en général; il se sent comme il est de violer toutes sortes d'hypothèses.Pourquoi ne pas simplement de la conception, plutôt que de se plier en quatre pour s'assurer qu'il ne se produise pas?
Le TPL est un hareng rouge ici, la réentrance peut se produire dans n'importe quel code à condition que vous pouvez créer un cycle, et que vous ne savez pas ce qui va arriver dans le " sud " de votre cadre de pile. Synchrone réentrance est le meilleur résultat ici - au moins, vous ne pouvez pas l'auto-blocage-vous (aussi facilement).
Verrous de gérer les threads synchronisation. Ils sont orthogonaux à la gestion de la réentrée. Sauf si vous êtes à la protection d'un véritable usage unique ressource (probablement un périphérique physique, dans ce cas, vous devriez probablement utiliser une file d'attente), pourquoi ne pas vous assurer que votre état de l'instance cohérente de réentrée peut "juste travail".
(Du côté de la pensée: les Sémaphores sont réentrant sans décrémentation?)
Vous pouvez facilement tester cela en écrivant un rapide de l'application qui a partagé une socket de connexion entre les threads et les tâches.
La tâche serait d'acquérir un verrou avant d'envoyer un message en bas de la douille et en attente d'une réponse. Une fois que cela bloque et devient inactif (IOBlock) définir une autre tâche dans le même bloc à faire de même. Il doit bloquer sur l'acquisition de la serrure, si elle ne le fait pas et la deuxième tâche est autorisé à passer l'écluse, car il géré par le même thread, alors vous avez un problème.
Solution avec
new CancellationToken()
proposée par Erwin n'a pas de travail pour moi), d'inlining est arrivé à se produire de toute façon.Donc j'ai fini par utiliser une autre condition conseillé par Jon et Stephen
(
... or if you pass in a non-infinite timeout ...
):Remarque: Omettant la gestion des exceptions, etc ici pour plus de simplicité, vous devriez l'esprit de ceux dans le code de production.