Quand une async méthode awaits a Task qu'arrive-t-il au thread sur lequel elle s'exécute actuellement?

Je suppose que sur un thread d'interface utilisateur, la boucle de message reprend et sur un thread de pool de threads, le thread est relâché dans le pool de threads. Mais que se passe-t-il si le thread a été démarré manuellement? Existe-t-il d'autres types de threads?

4
BetaKeja 21 avril 2017 à 07:37

3 réponses

Meilleure réponse

Cela m'a pris du temps à réaliser, mais cette partie de l'async-await est extrêmement simple, il suffit de remonter la pile d'appels. Chaque fois qu'une méthode awaits quelque chose (en supposant que ce n'est pas une tâche terminée ou quelque chose de similaire), elle retourne à l'appelant. Puisque nous parlons du point où votre code cède le contrôle du thread, cela signifie que c'est le dernier morceau de votre code au-dessus de la pile.

Si nous exécutons le thread d'interface utilisateur, nous retournons à la boucle de message. Si nous sommes sur le pool de threads, le contrôle des threads revient au pool de threads. Un thread créé manuellement exécute uniquement une méthode void, vous ne pouvez utiliser await que si c'est une méthode async void, ce qui signifie qu'il terminera le thread au premier await avant même que la méthode ne soit terminée.

Les suites fonctionnent de la même manière. Il le mettra en file d'attente sur le thread de l'interface utilisateur ou le pool de threads, puis il sera à nouveau awaits ou il se terminera, cédant à nouveau le contrôle.

Edit: J'ai fait quelques essais avec des planificateurs de tâches personnalisés et vous pouvez y appliquer exactement la même logique. Lorsque la tâche attend, elle cède le contrôle. Le planificateur de tâches que j'ai utilisé est basé sur ce planificateur de tâches à thread unique. Dans ce cas, la cession du contrôle signifie que le planificateur de tâches commence à travailler sur la tâche suivante dans la file d'attente. Il est également important de noter que les continuations sont planifiées avec le planificateur de tâches actuel, sauf si ConfigureAwait(false) est utilisé.

2
BetaKeja 25 avril 2017 à 15:23

En fait, n'importe quel thread peut exécuter une méthode asynchrone de manière synchrone et il attendra jusqu'à ce que

=>

Stephen Cleary a fait remarquer que cette partie n'est pas correcte: "attendez que la méthode asynchrone soit terminée". Le libellé est vraiment trompeur. Alors peut-être que ce ne sera guère mieux.

=>

Toutes les parties exécutées de manière synchrone sont terminées. Attend peut-être une tâche terminée, comme Task.FromResult. En outre, il pourrait s'agir d'une autre méthode asynchrone, qui pourrait être exécutée de manière complètement synchrone.

Mais dans un moment qui attend quelque part attend l'opération qui n'a pas pu être terminée avant que Waiter ne soit fourni, alors le t.RunSynchronously se fermera même si la méthode async ne s'est pas encore exécutée jusqu'à la fin.

void ThreadWorker(object obj)
{
    var t = new Task<Task>(AsyncMethod);
    t.RunSynchronously();
    //in this case, it runs the AsyncMethod completely
    //and effectively waits for result
}

async Task AsyncMethod()
{
   await Something().ConfigureAwait(false);
   //everything from here runs on the ThreadPool
}
-1
ipavlu 22 avril 2017 à 01:23

Veuillez noter qu'il n'existe pas de fil manuel. Ce ne sont que des fils. Comme les électrons, il n'y a pas deux types d'électrons :).

La différence entre le thread ThreadPool, votre thread, le thread d'interface utilisateur, le thread COM, etc. réside dans le fait que le thread a une boucle de message, un contexte de synchronisation. Si le thread a un contexte de synchronisation, alors tout ce que nous commençons dessus ne s'exécute pas directement, mais tout est démarré en tant que délégués d'une file d'attente et lorsqu'il n'y a rien dans la file d'attente, le thread attend.

Donc, si une partie de votre code dans la méthode async s'exécute sur un tel thread avec un contexte de synchronisation, alors wait, il stocke le contexte, à moins que ConfigureAwait et ne soit terminé avec cette méthode et attend le prochain délégué ou message pour l'exécution. Ainsi, lorsque l'attente est terminée et que ConfigureAwait n'a pas été utilisé pour déplacer le code suivant vers le pool de threads, il envoie un nouveau délégué au contexte de synchronisation, qui est le code après l'attente et le traitement.

0
ipavlu 21 avril 2017 à 19:17