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.