async / wait avec le paramètre continueOnCapturedContext de ConfigureAwait et SynchronizationContext pour les continuations asynchrones

Je voudrais d’abord mettre le code, puis expliquer la situation et poser ma question sur cette base:

public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private async void Button_Click_2(object sender, RoutedEventArgs e) { var result = await GetValuesAsync(); Foo.Text += result; } public async Task GetValuesAsync() { using (var httpClient = new HttpClient()) { var response = await httpClient .GetAsync("http://www.google.com") .ConfigureAwait(continueOnCapturedContext: false); // This is the continuation for the httpClient.GetAsync method. // We shouldn't get back to sync context here // Cuz the continueOnCapturedContext is set to *false* // for the Task which is returned from httpClient.GetAsync method var html = await GetSsortingngAsync(); // This is the continuation for the GetSsortingngAsync method. // Should I get back to sync context here? // Cuz the continueOnCapturedContext is set to *true* // for the Task which is returned from GetSsortingngAsync // However, GetSsortingngAsync may be executed in another thread // which has no knowledge for the sync context // because the continueOnCapturedContext is set to *false* // for the Task which is returned from httpClient.GetAsync method. // But, on the other hand, GetSsortingngAsync method also has a // chance to be executed in the UI thread but we shouldn't be // relying on that. html += "Hey..."; Foo.Text = html; return html; } } public async Task GetSsortingngAsync() { await Task.Delay(1000); return "Done..."; } } 

Ceci est un exemple assez simple de WPF qui fonctionne sous .NET 4.5 et qui n’a probablement pas beaucoup de sens, mais cela devrait m’aider à expliquer ma situation.

J’ai un bouton sur l’écran qui a un événement de clic asynchrone. Lorsque vous consultez le code GetValuesAsync , vous verrez deux fois l’utilisation du mot-clé wait. Lors de la première utilisation, j’ai défini continueOnCapturedContext paramètre continueOnCapturedContext de la méthode Task.ConfigureAwait sur false . Cela signifie donc que je ne veux pas nécessairement que ma suite soit exécutée à l’intérieur de SynchronizationContext.Current . Jusqu’ici tout va bien.

Lors de la seconde utilisation (avec la méthode GetSsortingngAsync ), je n’ai pas appelé la méthode ConfigureAwait . Donc, j’ai essentiellement indiqué que je souhaitais revenir au contexte de synchronisation actuel pour poursuivre la méthode GetSsortingngAsync . Ainsi, comme vous pouvez le constater, j’essaie de définir la TextBlock.Text (qui appartient au fil de l’interface utilisateur) dans la suite.

Lorsque j’exécute l’application et que je clique sur le bouton, une exception me donne le message suivant:

Le thread appelant ne peut pas accéder à cet object car un autre thread le possède.

Au début, cela n’avait aucun sens pour moi et je pensais avoir découvert un bogue, puis j’ai réalisé que GetSsortingngAsync pouvait être exécuté dans un autre thread (très probable) différent du thread de l’UI et n’ayant aucune connaissance du contexte de synchronisation car continueOnCapturedContext a la valeur false pour la Task renvoyée par la méthode httpClient.GetAsync .

Est-ce le cas ici? De plus, dans ce cas, y a-t-il une chance pour GetSsortingngAsync méthode GetSsortingngAsync soit renvoyée sur le thread d’interface utilisateur car la poursuite de la méthode httpClient.GetAsync peut être exécutée à l’intérieur du thread d’interface utilisateur?

J’ai aussi quelques commentaires à l’intérieur du code. Compte tenu de mes questions et des commentaires à l’intérieur du code, est-ce que je manque quelque chose ici?

Lorsque vous appelez ConfigureAwait(false) , le rest de la méthode sera exécuté sur un thread de pool de threads, sauf si la Task que vous await est déjà terminée.

Étant donné que GetAsync presque certainement de manière asynchrone, je m’attendrais à ce que GetSsortingngAsync s’exécute sur un thread de pool de threads.

 public async Task GetValuesAsync() { using (var httpClient = new HttpClient()) { var response = await httpClient .GetAsync("http://www.google.com") .ConfigureAwait(continueOnCapturedContext: false); // And now we're on the thread pool thread. // This "await" will capture the current SynchronizationContext... var html = await GetSsortingngAsync(); // ... and resume it here. // But it's not the UI SynchronizationContext. // It's the ThreadPool SynchronizationContext. // So we're back on a thread pool thread here. // So this will raise an exception. html += "Hey..."; Foo.Text = html; return html; } } 

De plus, dans ce cas, y a-t-il une chance pour que la méthode GetSsortingngAsync soit renvoyée sur le thread d’interface utilisateur car la poursuite de la méthode httpClient.GetAsync peut être exécutée à l’intérieur du thread d’interface utilisateur?

GetSsortingngAsync ne s’exécutera sur le thread d’interface utilisateur que si GetAsync terminé avant d’être GetAsync . Très improbable.

Pour cette raison, je préfère utiliser ConfigureAwait(false) pour chaque await une fois que le contexte n’est plus nécessaire.