Por que TaskScheduler.Current é o TaskScheduler padrão?

Biblioteca de Tarefas Paralelas é ótima e eu a usei bastante nos últimos meses. No entanto, há algo realmente me incomodando: o fato de queTaskScheduler.Current é o planejador de tarefas padrão, nãoTaskScheduler.Default. Isso não é absolutamente óbvio à primeira vista na documentação nem nas amostra

Current pode levar a erros sutis, pois seu comportamento está mudando dependendo de você estar em outra tarefa. O que não pode ser determinado facilmente.

Suponha que eu esteja gravando uma biblioteca de métodos assíncronos, usando o padrão assíncrono padrão com base em eventos para sinalizar a conclusão no contexto de sincronização original, exatamente da mesma maneira que os métodos XxxAsync fazem no .NET Framework (por exemplo,DownloadFileAsync). Decido usar a Biblioteca Paralela de Tarefas para implementação, porque é realmente fácil implementar esse comportamento com o seguinte código:

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

Até agora, tudo funciona bem. Agora, vamos fazer uma chamada para esta biblioteca clicando em um botão em um aplicativo WPF ou WinForms:

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*/);
    ...
}

Aqui, a pessoa que escreveu a chamada da biblioteca escolheu iniciar um novoTask quando a operação for concluída. Nada incomum. Ele segue exemplos encontrados em qualquer lugar da Web e simplesmente usaTask.Factory.StartNew sem especificar oTaskScheduler (e não há sobrecarga fácil para especificar no segundo parâmetro). ODoSomethingElse método @ funciona bem quando chamado sozinho, mas assim que é invocado pelo evento, a interface do usuário congela desdeTaskFactory.Current reutilizará o agendador de tarefas do contexto de sincronização da continuação da minha bibliotec

Encontrar isso pode levar algum tempo, especialmente se a segunda chamada de tarefa estiver oculta em uma pilha de chamadas complexa. Obviamente, a correção aqui é simples quando você sabe como tudo funciona: sempre especifiqueTaskScheduler.Default para qualquer operação que você espera executar no pool de threads. No entanto, talvez a segunda tarefa seja iniciada por outra biblioteca externa, sem saber sobre esse comportamento e usando ingenuamenteStartNew sem um agendador específico. Espero que este caso seja bastante comu

Depois de entender, não consigo entender a escolha da equipe que está escrevendo o TPL para usarTaskScheduler.Current ao invés deTaskScheduler.Default como padrão:

Não é óbvio,Default não é o padrão! E a documentação está seriamente faltando.O verdadeiro agendador de tarefas usado porCurrent depende da pilha de chamadas! É difícil manter invariantes com esse comportamento. É complicado especificar o agendador de tarefas comStartNew desde que você precise especificar as opções de criação de tarefas e o token de cancelamento primeiro, levando a linhas longas e menos legíveis. Isso pode ser aliviado escrevendo um método de extensão ou criando umTaskFactory que usaDefault. captura da pilha de chamadas tem custos adicionais de desempenh Quando realmente quero que uma tarefa dependa de outra tarefa em execução pai, prefiro especificá-la explicitamente para facilitar a leitura do código, em vez de confiar na mágica da pilha de chamada

Eu sei que essa pergunta pode parecer bastante subjetiva, mas não consigo encontrar um bom argumento objetivo sobre o porquê desse comportamento. Tenho certeza de que estou perdendo algo aqui: é por isso que estou me voltando para você.

questionAnswers(5)

yourAnswerToTheQuestion