Préface : Je cherche une explication, pas seulement une solution. Je connais déjà la solution.
Malgré avoir passé plusieurs jours à étudier des articles MSDN sur le modèle asynchrone basé sur les tâches (TAP), asynchroniser et attendre, je suis toujours un peu confus au sujet de certains des détails les plus fins.
J'écris un enregistreur pour les applications du Windows Store et je souhaite prendre en charge la journalisation asynchrone et synchrone. Les méthodes asynchrones suivent le TAP, les méthodes synchrones doivent masquer tout cela et ressembler et fonctionner comme des méthodes ordinaires.
Il s'agit de la méthode principale de la journalisation asynchrone:
private async Task WriteToLogAsync(string text)
{
StorageFolder folder = ApplicationData.Current.LocalFolder;
StorageFile file = await folder.CreateFileAsync("log.log",
CreationCollisionOption.OpenIfExists);
await FileIO.AppendTextAsync(file, text,
Windows.Storage.Streams.UnicodeEncoding.Utf8);
}
Maintenant, la méthode synchrone correspondante ...
Version 1 :
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Wait();
}
Cela semble correct, mais cela ne fonctionne pas. L'ensemble du programme se bloque pour toujours.
Version 2 :
Hmm .. Peut-être que la tâche n'a pas commencé?
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Start();
task.Wait();
}
Cela jette InvalidOperationException: Start may not be called on a promise-style task.
Version 3:
Hmm .. Task.RunSynchronously
semble prometteur.
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.RunSynchronously();
}
Cela jette InvalidOperationException: RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.
Version 4 (la solution):
private void WriteToLog(string text)
{
var task = Task.Run(async () => { await WriteToLogAsync(text); });
task.Wait();
}
Cela marche. Donc, 2 et 3 ne sont pas les bons outils. Mais 1? Quel est le problème avec 1 et quelle est la différence avec 4? Qu'est-ce qui fait que 1 cause un gel? Y a-t-il un problème avec l'objet de tâche? Y a-t-il un blocage non évident?
la source
Réponses:
L'
await
intérieur de votre méthode asynchrone tente de revenir au thread d'interface utilisateur.Étant donné que le thread d'interface utilisateur est occupé à attendre la fin de la tâche, vous avez un blocage.
Déplacer l'appel asynchrone pour
Task.Run()
résoudre le problème.Étant donné que l'appel asynchrone s'exécute maintenant sur un thread de pool de threads, il n'essaie pas de revenir au thread d'interface utilisateur, et tout fonctionne donc.
Alternativement, vous pouvez appeler
StartAsTask().ConfigureAwait(false)
avant d'attendre l'opération interne pour la faire revenir au pool de threads plutôt qu'au thread d'interface utilisateur, en évitant complètement le blocage.la source
ConfigureAwait(false)
la solution appropriée dans ce cas. Puisqu'il n'a pas besoin d'appeler les rappels dans le contexte capturé, il ne devrait pas. Étant une méthode API, elle doit la gérer en interne, plutôt que de forcer tous les appelants à sortir du contexte de l'interface utilisateur.Microsoft.Bcl.Async
.Appeler du
async
code à partir d'un code synchrone peut être assez délicat.J'explique les raisons complètes de cette impasse sur mon blog . En bref, il y a un "contexte" qui est enregistré par défaut au début de chacun
await
et utilisé pour reprendre la méthode.Donc, si cela est appelé dans un contexte d'interface utilisateur, une fois l'opération
await
terminée, laasync
méthode essaie de ressaisir ce contexte pour continuer l'exécution. Malheureusement, le code utilisantWait
(ouResult
) bloquera un thread dans ce contexte, laasync
méthode ne peut donc pas se terminer.Les directives pour éviter cela sont les suivantes:
ConfigureAwait(continueOnCapturedContext: false)
autant que possible. Cela permet à vosasync
méthodes de continuer à s'exécuter sans avoir à ressaisir le contexte.async
tout le chemin. Utilisezawait
au lieu deResult
ouWait
.Si votre méthode est naturellement asynchrone, vous (probablement) ne devriez pas exposer un wrapper synchrone .
la source
async
comment je pourrais faire cela et empêcher un incendie et oublier la situation.await
est pris en charge dans lescatch
blocs à partir de VS2015. Si vous êtes sur une version plus ancienne, vous pouvez affecter l'exception à une variable locale et faire leawait
bloc après le catch .Voici ce que j'ai fait
fonctionne très bien et ne bloque pas le thread d'interface utilisateur
la source
Avec un petit contexte de synchronisation personnalisé, la fonction de synchronisation peut attendre la fin de la fonction asynchrone, sans créer de blocage. Voici un petit exemple pour l'application WinForms.
la source