L'Appel De TaskCompletionSource.SetResult non bloquant de manière
J'ai découvert que TaskCompletionSource.SetResult();
appelle le code en attente de la tâche avant de la retourner. Dans mon cas, entraîner un blocage.
C'est une version simplifiée qui est démarré dans un ordinaire Thread
void ReceiverRun()
while (true)
{
var msg = ReadNextMessage();
TaskCompletionSource<Response> task = requests[msg.RequestID];
if(msg.Error == null)
task.SetResult(msg);
else
task.SetException(new Exception(msg.Error));
}
}
La "async" partie du code ressemble à ceci.
await SendAwaitResponse("first message");
SendAwaitResponse("second message").Wait();
L'Attente est effectivement imbriquées à l'intérieur des non-appels asynchrones.
La SendAwaitResponse(simplifié)
public static Task<Response> SendAwaitResponse(string msg)
{
var t = new TaskCompletionSource<Response>();
requests.Add(GetID(msg), t);
stream.Write(msg);
return t.Task;
}
Mon hypothèse était que la deuxième SendAwaitResponse exécuté dans un pool de threads thread, mais elle continue dans le thread créé pour ReceiverRun.
Est-il de toute façon à définir le résultat d'une tâche sans poursuivre son attendu de code?
L'application est un application console.
- Un très simple solution de contournement serait de faire
Task.Run(() => task.SetResult(msg));
(et de la même façon avecSetException()
). Mais j'espère qu'il y est une meilleure option.
Vous devez vous connecter pour publier un commentaire.
Oui, j'ai un post de blog cette documentation (autant que je sache, il n'est pas documentée sur le site MSDN). Le blocage se produit parce que de deux choses l'une:
async
et le blocage de code (c'est à dire, unasync
méthode appelleWait
).TaskContinuationOptions.ExecuteSynchronously
.Je vous recommande de commencer par le plus simple possible solution: enlever la première chose (1). I. e., ne pas mélanger
async
etWait
appels:Au lieu de cela, utiliser
await
constante:Si vous le souhaitez, vous pouvez
Wait
à un autre point le plus haut dans la pile d'appel (pas dans unasync
méthode).C'est mon plus solution recommandée. Toutefois, si vous voulez essayer de retirer la deuxième chose (2), vous pouvez faire une couple de trucs: soit envelopper le
SetResult
dans unTask.Run
de force sur un thread séparé (mon AsyncEx bibliothèque a*WithBackgroundContinuations
les méthodes d'extension qui font exactement cela), ou de donner votre fil d'un contexte réel (comme monAsyncContext
type) et spécifierConfigureAwait(false)
, qui la cause de la poursuite d'ignorer leExecuteSynchronously
drapeau.Mais ces solutions sont beaucoup plus complexe que la simple séparation de la
async
et le blocage de code.Comme une note de côté, prendre un coup d'oeil à TPL Dataflow; on dirait que vous le trouverez utile.
Que votre app est une application de console, il fonctionne sur le défaut contexte de synchronisation, où la
await
poursuite de callback sera appelé sur le même thread que l'attente de la tâche est devenue achevée le. Si vous voulez changer de threads aprèsawait SendAwaitResponse
, vous pouvez le faire avecawait Task.Yield()
:Vous pourriez l'améliorer encore en stockant
Thread.CurrentThread.ManagedThreadId
à l'intérieur deTask.Result
et en la comparant à la thread en cours d'identification d'après lesawait
. Si vous êtes toujours sur le même thread, neawait Task.Yield()
.Bien que je comprenne que
SendAwaitResponse
est une version simplifiée de votre code, il est encore complètement synchrone à l'intérieur (la façon dont vous l'a montré dans votre question). Pourquoi voudriez-vous attendre de n'importe quel commutateur fil là-dedans?De toute façon, vous devriez revoir votre logique de la façon dont il n'est pas de faire des hypothèses sur ce fil que vous êtes actuellement sur. Éviter de mélanger
await
etTask.Wait()
et de faire toutes vos code asynchrone. Généralement, il est possible de s'en tenir à unWait()
quelque part sur le niveau supérieur (p. ex. à l'intérieurMain
).[ÉDITÉ] Appel
task.SetResult(msg)
deReceiverRun
transfère le flux de contrôle à l'endroit où vousawait
sur letask
- sans fil-commutateur, en raison de la synchronisation par défaut le contexte du comportement. Ainsi, votre code qui effectue le traitement du message est de prendre laReceiverRun
fil. Finalement,SendAwaitResponse("second message").Wait()
est appelée sur le même thread, provoquant le blocage.Ci-dessous est une console de code de l'application, sur le modèle de votre échantillon. Il utilise
await Task.Yield()
à l'intérieur deProcessAsync
à l'annexe de la poursuite sur un thread séparé, de sorte que le flux de contrôle renvoie àReceiverRun
et il n'y a pas de blocage.Ce n'est pas très différent de faire
Task.Run(() => task.SetResult(msg))
à l'intérieur deReceiverRun
. Le seul avantage que je peux penser, c'est que vous avez un contrôle explicite quand passer les fils. De cette façon, vous pouvez rester sur le même fil pour aussi longtemps que possible (par exemple, pourtask2
,task3
,task4
, mais vous avez encore besoin d'un autre thread de bascule aprèstask4
pour éviter un blocage surtask5.Wait()
).Les deux solutions, éventuellement, de faire le pool de threads de croissance, ce qui est mauvais en termes de performances et d'évolutivité.
Maintenant, si nous remplacer
task.Wait()
avecawait task
partout à l'intérieurProcessAsync
dans le code ci-dessus, nous n'aurons pas à utiliserawait Task.Yield
et il faudra encore pas de blocages. Cependant, l'ensemble de la chaîne deawait
appels après le 1erawait task1
à l'intérieur deProcessAsync
sera effectivement réalisée sur leReceiverRun
fil. Tant que nous n'avons pas bloquer ce fil avec d'autresWait()
de style appels et ne fais pas beaucoup de CPU de travail comme nous allons le traitement des messages, cette approche pourrait fonctionner OK (asynchrone IO-liéawait
de style appels devraient être toujours OK, et ils peuvent effectivement déclencher un implicite fil de l'interrupteur).Cela dit, je pense que vous auriez besoin d'un thread séparé avec une sérialisation contexte de synchronisation est installé pour le traitement des messages (similaire à
WindowsFormsSynchronizationContext
). C'est là que votre code asynchrone contenantawaits
doit s'exécuter. Vous souhaitez toujours besoin d'éviter d'utiliserTask.Wait
sur ce thread. Et si un message individuel de traitement prend beaucoup de CPU de travail, vous devez utiliserTask.Run
pour de tels travaux. Pour async IO appels entrants, vous pouvez rester sur le même fil.Vous pouvez regarder la
ActionDispatcher
/ActionDispatcherSynchronizationContext
de @StephenCleary'sNito Asynchrone Bibliothèque pour votre message asynchrone logique de traitement. Heureusement, Stephen sauts et fournit une meilleure réponse.
"Mon hypothèse était que la deuxième SendAwaitResponse exécuté dans un pool de threads thread, mais elle continue dans le thread créé pour ReceiverRun."
Il dépend entièrement de ce que vous faites au sein de SendAwaitResponse. Asynchronisme et de la simultanéité ne sont pas la même chose.
Découvrez: C# 5 Async/Await - est-il *simultanées*?
WriteAsync()
ne serait pas résoudre le problème.Un peu en retard à la fête, mais voici ma solution qui je pense est la valeur ajoutée.
J'ai eu du mal avec cela aussi, je l'ai résolu en s'emparant de l'SynchronizationContext sur la méthode qui est attendu.
Il ressemblerait à quelque chose comme: