Warum ist TaskScheduler.Current der Standard TaskScheduler?

Die Task Parallel Library ist toll und ich habe es verwendet eine Menge in den letzten Monaten. Allerdings gibt es etwas wirklich stört mich: die Tatsache, dass Aufgabenplaner.Current ist die Standard-task-scheduler, nicht Aufgabenplaner.Default. Das ist absolut nicht offensichtlich auf den ersten Blick in die Dokumentation noch Proben.

Current führen kann, zu subtilen bugs, da Ihr Verhalten ändert sich je nachdem, ob Sie in einer anderen Aufgabe. Das kann nicht leicht ermittelt werden.

Nehmen wir an ich Schreibe eine Bibliothek für asynchrone Methoden, mit der die standard async-pattern, basierend auf Ereignisse zu signalisieren Abschluss auf der original-synchronisation Kontext, in genau der gleichen Weise XxxAsync-Methode in der .NET Framework (z.B. DownloadFileAsync). Ich entscheiden, zu verwenden der Task Parallel Library für die Umsetzung, weil es wirklich einfach zu implementieren dieses Verhalten mit dem folgenden code:

public class MyLibrary {
    public event EventHandler SomeOperationCompleted;

    private void OnSomeOperationCompleted() {
        var handler = SomeOperationCompleted;
        if (handler != null)
            handler(this, EventArgs.Empty);
    }

    public void DoSomeOperationAsync() {
                    Task.Factory
                        .StartNew
                         (
                            () => Thread.Sleep(1000) //simulate a long operation
                            , CancellationToken.None
                            , TaskCreationOptions.None
                            , TaskScheduler.Default
                          )
                        .ContinueWith
                           (t => OnSomeOperationCompleted()
                            , TaskScheduler.FromCurrentSynchronizationContext()
                            );
    }
}

Bisher klappt alles gut. Nun, lassen Sie uns einen Anruf tätigen, um diese Bibliothek auf eine Schaltfläche klicken Sie in einer WPF oder WinForms-Anwendung:

private void Button_OnClick(object sender, EventArgs args) {
    var myLibrary = new MyLibrary();
    myLibrary.SomeOperationCompleted += (s, e) => DoSomethingElse();
    myLibrary.DoSomeOperationAsync();
}

private void DoSomethingElse() {
    ...
    Task.Factory.StartNew(() => Thread.Sleep(5000)/*simulate a long operation*/);
    ...
}

Hier, die person schriftlich die Bibliothek nennen, entschied sich für den start einer neuen Task wenn der Vorgang abgeschlossen ist. Nichts ungewöhnliches. Er oder Sie folgt Beispiele finden sich überall auf dem web und verwenden Sie einfach Task.Factory.StartNew ohne Angabe der TaskScheduler (und es gibt keine einfache überlastung angeben, um den zweiten parameter). Die DoSomethingElse Methode funktioniert gut, wenn man allein ist, aber sobald es aufgerufen wird von der Veranstaltung, die UI friert seit TaskFactory.Current wird die Wiederverwendung der synchronization context task scheduler aus meiner Bibliothek Fortsetzung.

Herauszufinden, dies könnte einige Zeit dauern, vor allem, wenn Sie die zweite Aufgabe Aufruf begraben unten einige komplexere Aufruf-stack. Natürlich, die Lösung ist einfach, wenn man weiß, wie alles funktioniert: immer angeben TaskScheduler.Default für jede operation, die Sie erwarten sind, um auf die thread-pool. Aber vielleicht ist die zweite Aufgabe gestartet wird von einem anderen externen Bibliothek, nicht zu wissen, über dieses Verhalten und naiv mit StartNew ohne einen konkreten scheduler. Ich gehe davon aus, dass dieser Fall ziemlich Häufig.

Nach dem wickeln meinem Kopf herum, die ich nicht verstehen kann, ist die Wahl des Teams schreiben die TPL zu verwenden TaskScheduler.Current statt TaskScheduler.Default als Standard:

  • Es ist nicht unbedingt offensichtlich, Default ist nicht der Standard! Und die Dokumentation ist ernst fehlt.
  • Die eigentliche task-scheduler verwendet Current hängt von den call-stack! Es ist schwer zu pflegen Invarianten mit diesem Verhalten.
  • Es ist umständlich, geben Sie den task-scheduler mit StartNew da müssen Sie die task-Optionen für die Erstellung und Stornierung token erste, was zu lang ist, weniger lesbare Zeilen. Diese gelindert werden können durch das schreiben einer extension-Methode oder das erstellen einer TaskFactory verwendet Default.
  • Erfassung der call-stack ist eine zusätzliche Leistung Kosten.
  • Wenn ich wirklich wollen, eine Aufgabe, abhängig zu sein, auf den anderen Elternteil laufende Aufgabe, die ich lieber angeben, es ausdrücklich zu erleichtern, code zu Lesen, anstatt verlassen sich auf call-stack-magic.

Ich weiß, diese Frage klingt vielleicht sehr subjektiv, aber ich kann nicht finden, eine gute, Objektive Argumente, warum dieses Verhalten so ist, wie es ist. Ich bin sicher, ich bin hier etwas fehlt: das ist, warum drehe ich mich zu dir.

InformationsquelleAutor der Frage Julien Lebosquain | 2011-07-23

Schreibe einen Kommentar