2014-04-14 6 views
11
Verständnis

Hier ist eine einfache WinForms App:das Verhalten von TaskScheduler.Current

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(); 
     } 
    } 
} 

Die Debug ouput (wenn die Schaltfläche geklickt wird):

 
{ where = 1) before await, currentTs = System.Threading.Tasks.SynchronizationContextTaskScheduler, thread = 9, context = System.Windows.Forms.WindowsFormsSynchronizationContext } 
{ where = 2) after await, currentTs = System.Threading.Tasks.ThreadPoolTaskScheduler, thread = 9, context = System.Windows.Forms.WindowsFormsSynchronizationContext } 

Die Frage: Warum istTaskScheduler.Current Wechsel von SynchronizationContextTaskScheduler bis ThreadPoolTaskScheduler nach await hier?

Dies zeigt im Wesentlichen das Verhalten TaskCreationOptions.HideScheduler für await Fortsetzung, die meiner Meinung nach unerwartet und unerwünscht ist.

Diese Frage wurde durch eine andere Frage von mir ausgelöst:

AspNetSynchronizationContext and await continuations in ASP.NET.

Antwort

12

Wenn keine aktuelle Aufgabe ausgeführt wird, ist TaskScheduler.Current dasselbe wie TaskScheduler.Default. Mit anderen Worten, ThreadPoolTaskScheduler fungiert tatsächlich sowohl als Thread-Pool-Task-Scheduler als auch als der Wert Bedeutung "kein aktueller Task-Scheduler".

Der erste Teil des Delegaten async wird explizit unter Verwendung der SynchronizationContextTaskScheduler geplant und auf dem UI-Thread mit einem Aufgabenplaner und Synchronisierungskontext ausgeführt. Der Taskplaner leitet den Delegaten an den Synchronisationskontext weiter.

Wenn der await seinen Kontext erfasst, erfasst er den Synchronisationskontext (nicht den Taskplaner) und verwendet diesen Syncctx, um fortzufahren. Daher wird die Methodenfortsetzung an diesen Syncctx gesendet, der sie im UI-Thread ausführt.

Wenn die Fortsetzung im UI-Thread ausgeführt wird, verhält sie sich sehr ähnlich wie ein Ereignishandler. Der Delegat wird direkt ausgeführt und nicht in eine Aufgabe eingeschlossen. Wenn Sie TaskScheduler.Current am Anfang von button1_Click überprüfen, finden Sie es auch ThreadPoolTaskScheduler.

BTW, ich empfehle Ihnen, dieses Verhalten (Ausführen von Delegaten direkt, nicht in Aufgaben eingebunden) als ein Implementierungsdetail behandeln.

+0

Dies ist, wie ich dachte, es funktioniert, aber wollte nicht ohne ein bisschen mehr Informationen kommentieren. +1. –

+0

Ich sehe, anscheinend funktioniert so 'TaskAwaiter'. IMO, das Design, das sie für "fließendes" 'TaskScheduler.Current' entworfen haben, ist eher verwirrend: Normalerweise ist es nicht das, was Sie logisch erwarten würden. – Noseratio

+4

Einverstanden; 'TaskScheduler.Current' wurde mit dynamischer Parallelität entworfen, so dass untergeordnete Tasks den Scheduler von ihren übergeordneten Tasks erben. Dieses Standardverhalten ist für asynchrone Tasks verwirrend, weshalb ich darauf bestehe, den Scheduler in jedem 'StartNew' und' ContinueWith' explizit anzugeben. –

Verwandte Themen