Qual é a diferença entre Task.Run () e Task.Factory.StartNew ()

192

Eu tenho o método:

private static void Method()
{
    Console.WriteLine("Method() started");

    for (var i = 0; i < 20; i++)
    {
        Console.WriteLine("Method() Counter = " + i);
        Thread.Sleep(500);
    }

    Console.WriteLine("Method() finished");
}

E eu quero iniciar esse método em uma nova tarefa. Eu posso começar uma nova tarefa como esta

var task = Task.Factory.StartNew(new Action(Method));

ou isto

var task = Task.Run(new Action(Method));

Mas existe alguma diferença entre Task.Run()e Task.Factory.StartNew(). Ambos estão usando ThreadPool e iniciam Method () imediatamente após a criação da instância da tarefa. Quando devemos usar a primeira variante e quando a segunda?

Sergiy Lichenko
fonte
6
Na verdade, o StartNew não precisa usar o ThreadPool; consulte o blog ao qual vinculei minha resposta. O problema é, StartNewpor padrão, usos TaskScheduler.Currentque podem ser o pool de threads, mas também podem ser o thread da interface do usuário.
21816 Scott Chamberlain

Respostas:

197

O segundo método, Task.Runfoi introduzido em uma versão posterior da estrutura .NET (no .NET 4.5).

No entanto, o primeiro método Task.Factory.StartNew,, oferece a oportunidade de definir muitas coisas úteis sobre o encadeamento que você deseja criar, enquanto Task.Runnão fornece isso.

Por exemplo, digamos que você deseja criar um encadeamento de tarefas de execução longa. Se um encadeamento do conjunto de encadeamentos for usado para esta tarefa, isso poderá ser considerado um abuso do conjunto de encadeamentos.

Uma coisa que você poderia fazer para evitar isso seria executar a tarefa em um thread separado. Um encadeamento recém-criado que seria dedicado a esta tarefa e seria destruído assim que sua tarefa fosse concluída. Você não pode conseguir isso com o Task.Run, enquanto pode fazê-lo com o Task.Factory.StartNew, como abaixo:

Task.Factory.StartNew(..., TaskCreationOptions.LongRunning);

Como se afirma aqui :

Portanto, na visualização do desenvolvedor do .NET Framework 4.5, introduzimos o novo método Task.Run. Isso de forma alguma obsoleta o Task.Factory.StartNew, mas deve ser pensado como uma maneira rápida de usar o Task.Factory.StartNew sem precisar especificar vários parâmetros. É um atalho. De fato, o Task.Run é realmente implementado em termos da mesma lógica usada para Task.Factory.StartNew, apenas transmitindo alguns parâmetros padrão. Quando você passa uma ação para Task.Run:

Task.Run(someAction);

isso é exatamente equivalente a:

Task.Factory.StartNew(someAction, 
    CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
Christos
fonte
4
Eu tenho um pedaço de código onde o statemente that’s exactly equivalent tonão é válido.
Emaborsa
7
@ Emaborsa Eu agradeceria Se você pudesse postar este pedaço de código e elaborar seu argumento. Desde já, obrigado !
Christos
4
@Emaborsa Você pode criar um gist, gist.github.com e compartilhá-lo. No entanto, exceto por compartilhar essa essência, especifique como você chegou ao resultado que a frase tha's exactly equivalent tonão contém. Desde já, obrigado. Seria bom explicar com comentários no seu código. Obrigado :)
Christos
8
Também vale a pena mencionar que o Task.Run desembrulha a tarefa aninhada por padrão. Eu recomendo ler este artigo sobre grandes diferenças: blogs.msdn.microsoft.com/pfxteam/2011/10/24/...
Pawel Maga
1
@ The0bserver não, é TaskScheduler.Default. Consulte aqui o sourcesource.microsoft.com/#mscorlib/system/threading/Tasks/… .
Christos
46

As pessoas já mencionaram isso

Task.Run(A);

É equivalente a

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

Mas ninguém mencionou isso

Task.Factory.StartNew(A);

É equivalente a:

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Current);

Como você pode ver, dois parâmetros são diferentes para Task.Rune Task.Factory.StartNew:

  1. TaskCreationOptions- Task.Runusos, o TaskCreationOptions.DenyChildAttachque significa que as tarefas filho não podem ser anexadas ao pai, considere o seguinte:

    var parentTask = Task.Run(() =>
    {
        var childTask = new Task(() =>
        {
            Thread.Sleep(10000);
            Console.WriteLine("Child task finished.");
        }, TaskCreationOptions.AttachedToParent);
        childTask.Start();
    
        Console.WriteLine("Parent task finished.");
    });
    
    parentTask.Wait();
    Console.WriteLine("Main thread finished.");

    Quando invocamos parentTask.Wait(), childTasknão será aguardado, mesmo que tenhamos especificado TaskCreationOptions.AttachedToParentisso, porque isso TaskCreationOptions.DenyChildAttachproíbe que crianças se apeguem a ele. Se você executar o mesmo código em Task.Factory.StartNewvez de Task.Run, parentTask.Wait()esperará childTaskporque Task.Factory.StartNewusaTaskCreationOptions.None

  2. TaskScheduler- Task.Runusa, o TaskScheduler.Defaultque significa que o agendador de tarefas padrão (aquele que executa tarefas no Pool de Threads) sempre será usado para executar tarefas. Task.Factory.StartNewpor outro lado, usa o TaskScheduler.Currentque significa planejador do encadeamento atual, pode ser, TaskScheduler.Defaultmas nem sempre. Na verdade, quando o desenvolvimento Winformsou WPFaplicações, é necessário para atualização UI do segmento atual, para fazer isso as pessoas usam TaskScheduler.FromCurrentSynchronizationContext()agendador de tarefas, se você acidentalmente criar outra tarefa longa em execução dentro tarefa que costumava TaskScheduler.FromCurrentSynchronizationContext()programador da interface do usuário será congelado. Uma explicação mais detalhada disso pode ser encontrada aqui

Portanto, geralmente, se você não estiver usando a tarefa filho aninhada e sempre desejar que suas tarefas sejam executadas no Conjunto de Encadeamentos, é melhor usá-lo Task.Run, a menos que você tenha alguns cenários mais complexos.

Mykhailo Seniutovych
fonte
1
Esta é uma dica fantástica, deve ser a resposta aceita
Ali Bayat 18/02
30

Veja este artigo do blog que descreve a diferença. Basicamente fazendo:

Task.Run(A)

É o mesmo que fazer:

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);   
Scott Chamberlain
fonte
28

Foi Task.Runintroduzido na versão mais recente do .NET framework e é recomendado .

Começando com o .NET Framework 4.5, o método Task.Run é a maneira recomendada de iniciar uma tarefa vinculada à computação. Use o método StartNew somente quando você precisar de controle refinado para uma tarefa de execução de longo prazo e vinculada à computação.

O Task.Factory.StartNewtem mais opções, o Task.Runé uma abreviação:

O método Run fornece um conjunto de sobrecargas que facilitam o início de uma tarefa usando valores padrão. É uma alternativa leve às sobrecargas StartNew.

Por atalho, quero dizer um atalho técnico :

public static Task Run(Action action)
{
    return Task.InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default,
        TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark);
}
Zein Makki
fonte
21

De acordo com este post de Stephen Cleary, Task.Factory.StartNew () é perigoso:

Vejo muito código em blogs e em perguntas SO que usam Task.Factory.StartNew para acelerar o trabalho em um thread em segundo plano. Stephen Toub tem um excelente artigo de blog que explica por que o Task.Run é melhor que o Task.Factory.StartNew, mas acho que muitas pessoas simplesmente não o leram (ou não o entendem). Então, adotei os mesmos argumentos, adicionei uma linguagem mais vigorosa e veremos como isso acontece. :) O StartNew oferece muito mais opções que o Task.Run, mas é bastante perigoso, como veremos. Você deve preferir Task.Run sobre Task.Factory.StartNew no código assíncrono.

Aqui estão os motivos reais:

  1. Não entende delegados assíncronos. Na verdade, é o mesmo que o ponto 1 nos motivos pelos quais você deseja usar o StartNew. O problema é que, quando você passa um delegado assíncrono para StartNew, é natural supor que a tarefa retornada represente esse delegado. No entanto, como o StartNew não entende os delegados assíncronos, o que essa tarefa realmente representa é apenas o começo desse delegado. Essa é uma das primeiras armadilhas que os codificadores encontram ao usar o StartNew no código assíncrono.
  2. Planejador padrão confuso. OK, hora da pergunta complicada: no código abaixo, em qual thread o método “A” é executado?
Task.Factory.StartNew(A);

private static void A() { }

Bem, você sabe que é uma pergunta complicada, não é? Se você respondeu "um thread do pool de threads", desculpe, mas isso não está correto. "A" será executado em qualquer tarefa que o TaskScheduler esteja executando atualmente!

Isso significa que ele pode ser executado potencialmente no thread da interface do usuário se uma operação for concluída e ele retornará ao thread da interface do usuário devido a uma continuação, como Stephen Cleary explica mais detalhadamente em sua postagem.

No meu caso, eu estava tentando executar tarefas em segundo plano ao carregar um datagrid para uma exibição enquanto exibia uma animação ocupada. A animação ocupada não foi exibida ao usar, Task.Factory.StartNew()mas foi exibida corretamente quando mudei para Task.Run().

Para obter detalhes, consulte https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html

user8128167
fonte
1

Além das semelhanças, ou seja, Task.Run () é uma abreviação de Task.Factory.StartNew (), há uma pequena diferença entre o comportamento deles no caso de delegados de sincronização e assíncrona.

Suponha que existem dois métodos a seguir:

public async Task<int> GetIntAsync()
{
    return Task.FromResult(1);
}

public int GetInt()
{
    return 1;
}

Agora considere o seguinte código.

var sync1 = Task.Run(() => GetInt());
var sync2 = Task.Factory.StartNew(() => GetInt());

Aqui, sync1 e sync2 são do tipo Task <int>

No entanto, a diferença ocorre no caso de métodos assíncronos.

var async1 = Task.Run(() => GetIntAsync());
var async2 = Task.Factory.StartNew(() => GetIntAsync());

Nesse cenário, async1 é do tipo Tarefa <int>, mas async2 é do tipo Tarefa <Tarefa <int>>

Shubham Sharma
fonte
Sim, porque Task.Runincorporou a funcionalidade de desempacotamento do Unwrapmétodo. Aqui está um post do blog que explica o raciocínio por trás dessa decisão.
Theodor Zoulias
-8

Na minha aplicação que chama dois serviços, comparei Task.Run e Task.Factory.StartNew . Descobri que, no meu caso, os dois funcionam bem. No entanto, o segundo é mais rápido.

Devendra Rusia
fonte
Não sei por que essa resposta merece votos "10", mesmo que possa não ser correta ou útil ...
Mayer Spitzer