Comprendre le comportement de TaskScheduler.Current

Voici une application WinForms simple:

using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApplication { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private async void button1_Click(object sender, EventArgs e) { var ts = TaskScheduler.FromCurrentSynchronizationContext(); await Task.Factory.StartNew(async () => { Debug.WriteLine(new { where = "1) before await", currentTs = TaskScheduler.Current, thread = Thread.CurrentThread.ManagedThreadId, context = SynchronizationContext.Current }); await Task.Yield(); // or await Task.Delay(1) Debug.WriteLine(new { where = "2) after await", currentTs = TaskScheduler.Current, thread = Thread.CurrentThread.ManagedThreadId, context = SynchronizationContext.Current }); }, CancellationToken.None, TaskCreationOptions.None, scheduler: ts).Unwrap(); } } } 

La sortie de débogage (lorsque le bouton est cliqué):

 {où = 1) avant wait, currentTs = System.Threading.Tasks.SynchronizationContextTaskScheduler, thread = 9, context = System.Windows.Forms.WindowsFormsSynchronizationContext}
 {où = 2) après wait, currentTs = System.Threading.Tasks.ThreadPoolTaskScheduler, thread = 9, context = System.Windows.Forms.WindowsFormsSynchronizationContext}

La question: Pourquoi TaskScheduler.Current passe-t-il de SynchronizationContextTaskScheduler à ThreadPoolTaskScheduler après await ici?

Cela présente essentiellement le comportement TaskCreationOptions.HideScheduler pour await continuation, ce qui est inattendu et indésirable, à mon avis.

Cette question a été déclenchée par une autre de mes questions:

AspNetSynchronizationContext et attend des continuations dans ASP.NET .

Si aucune tâche n’est en cours d’exécution, TaskScheduler.Current est identique à TaskScheduler.Default . En d’autres termes, ThreadPoolTaskScheduler agit à la fois comme planificateur de tâches du pool de threads et comme valeur “pas de planificateur de tâches en cours”.

La première partie du délégué async est explicitement planifiée à l’aide de SynchronizationContextTaskScheduler et s’exécute sur le thread d’interface utilisateur avec un planificateur de tâches et un contexte de synchronisation. Le planificateur de tâches transmet le délégué au contexte de synchronisation.

Lorsque l’ await capture son contexte, elle capture le contexte de synchronisation (pas le planificateur de tâches) et utilise ce syncctx pour reprendre. Ainsi, la continuation de la méthode est publiée sur le syncctx, qui l’exécute sur le thread d’interface utilisateur.

Lorsque la suite est exécutée sur le thread d’interface utilisateur, son comportement est très similaire à celui d’un gestionnaire d’événements. le délégué est exécuté directement, pas encapsulé dans une tâche. Si vous cochez TaskScheduler.Current au début de button1_Click , vous constaterez qu’il s’agit également de ThreadPoolTaskScheduler .

En passant, je vous recommande de traiter ce problème (exécution directe de delegates, pas encapsulé dans des tâches) comme un détail d’implémentation.