Qual è la differenza tra Task.Run () e Task.Factory.StartNew ()


192

Ho un metodo:

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 voglio iniziare questo metodo in una nuova attività. Posso iniziare una nuova attività in questo modo

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

o questo

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

Ma c'è qualche differenza tra Task.Run()e Task.Factory.StartNew(). Entrambi utilizzano ThreadPool e avviano Method () immediatamente dopo aver creato l'istanza dell'attività. Quando dovremmo usare la prima variante e quando la seconda?


6
In realtà, StartNew non deve utilizzare ThreadPool, vedi il blog a cui mi sono collegato nella mia risposta. Il problema è StartNewper impostazione predefinita TaskScheduler.Currentche può essere il pool di thread ma potrebbe anche essere il thread dell'interfaccia utente.
Scott Chamberlain,

Risposte:


197

Il secondo metodo, Task.Runè stato introdotto in una versione successiva di .NET framework (in .NET 4.5).

Tuttavia, il primo metodo, Task.Factory.StartNewti dà l'opportunità di definire molte cose utili sul thread che vuoi creare, mentre Task.Runnon lo fornisce.

Ad esempio, supponiamo che tu voglia creare un thread di attività di lunga durata. Se un thread del pool di thread verrà utilizzato per questa attività, questo potrebbe essere considerato un abuso del pool di thread.

Una cosa che potresti fare per evitare ciò sarebbe quella di eseguire l'attività in un thread separato. Un thread appena creato che sarebbe dedicato a questa attività e verrebbe distrutto una volta completata l'attività. Non puoi farlo con il Task.Run, mentre puoi farlo con il Task.Factory.StartNew, come di seguito:

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

Come indicato qui :

Pertanto, in .NET Framework 4.5 Developer Preview, abbiamo introdotto il nuovo metodo Task.Run. Ciò non oscura in alcun modo Task.Factory.StartNew, ma piuttosto dovrebbe essere semplicemente considerato un modo rapido per usare Task.Factory.StartNew senza dover specificare un mucchio di parametri. È una scorciatoia. Infatti, Task.Run è effettivamente implementato in termini della stessa logica utilizzata per Task.Factory.StartNew, passando solo alcuni parametri predefiniti. Quando passi un'azione a Task.Run:

Task.Run(someAction);

è esattamente equivalente a:

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

4
Ho un pezzo di codice in cui lo statemente that’s exactly equivalent tonon regge.
Emaborsa,

7
@Emaborsa Gradirei se potessi pubblicare questo pezzo di codice ed elaborare il tuo argomento. Grazie in anticipo !
Christos,

4
@Emaborsa Potresti creare un gist, gist.github.com e condividerlo. Tuttavia, ad eccezione della condivisione di questa sintesi, si prega di specificare come si è arrivati ​​al risultato che la frase tha's exactly equivalent tonon contiene. Grazie in anticipo. Sarebbe bello spiegare con un commento sul tuo codice. Grazie :)
Christos,

8
Vale anche la pena ricordare che Task.Run scartare l'attività nidificata per impostazione predefinita. Consiglio di leggere questo articolo sulle principali differenze: blogs.msdn.microsoft.com/pfxteam/2011/10/24/…
Pawel Maga

1
@ The0bserver no, lo è TaskScheduler.Default. Dai un'occhiata qui riferimentiource.microsoft.com/#mscorlib/system/threading/Tasks/… .
Christos,

46

La gente lo ha già detto

Task.Run(A);

È equivalente a

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

Ma nessuno lo ha menzionato

Task.Factory.StartNew(A);

È equivalente a:

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

Come puoi vedere, due parametri sono diversi per Task.Rune Task.Factory.StartNew:

  1. TaskCreationOptions- Task.Runutilizza il TaskCreationOptions.DenyChildAttachche significa che i compiti dei bambini non possono essere associati al genitore, considerare questo:

    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 invochiamo parentTask.Wait(), childTasknon saremo attesi, anche se abbiamo specificato TaskCreationOptions.AttachedToParentper questo, questo è perché TaskCreationOptions.DenyChildAttachproibisce ai bambini di attaccarsi ad esso. Se si esegue lo stesso codice con Task.Factory.StartNewinvece di Task.Run, parentTask.Wait()attenderà childTaskperché Task.Factory.StartNewutilizzaTaskCreationOptions.None

  2. TaskScheduler- Task.Runutilizza il TaskScheduler.Defaultche significa che lo scheduler predefinito (quello che esegue le attività nel pool di thread) verrà sempre utilizzato per eseguire le attività. Task.Factory.StartNewd'altra parte usa il TaskScheduler.Currentche significa che lo scheduler del thread corrente, potrebbe essere, TaskScheduler.Defaultma non sempre. In effetti, durante lo sviluppo Winformso le WPFapplicazioni, è necessario aggiornare l'interfaccia utente dal thread corrente, per fare questo le persone usano l'utilità di TaskScheduler.FromCurrentSynchronizationContext()pianificazione, se si crea involontariamente un'altra attività di lunga durata all'interno dell'attività che ha utilizzato l'utilità di pianificazione, TaskScheduler.FromCurrentSynchronizationContext()l'interfaccia utente verrà bloccata. Una spiegazione più dettagliata di questo può essere trovata qui

Quindi, in genere, se non si sta utilizzando l'attività figlio annidato e si desidera sempre che le attività vengano eseguite sul pool di thread, è meglio utilizzarlo Task.Run, a meno che non si disponga di scenari più complessi.


1
Questo è un consiglio fantastico, dovrebbe essere la risposta accettata
Ali Bayat,

30

Vedi questo articolo del blog che descrive la differenza. Fondamentalmente facendo:

Task.Run(A)

È come fare:

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

28

È Task.Runstato introdotto nella versione .NET Framework più recente ed è consigliato .

A partire da .NET Framework 4.5, il metodo Task.Run è il modo consigliato per avviare un'attività associata al calcolo. Utilizzare il metodo StartNew solo quando è necessario un controllo approfondito per un'attività a elaborazione estesa con esecuzione prolungata.

L' Task.Factory.StartNewha più opzioni, l' Task.Runè una scorciatoia:

Il metodo Run fornisce una serie di sovraccarichi che semplificano l'avvio di un'attività utilizzando i valori predefiniti. È un'alternativa leggera ai sovraccarichi StartNew.

E per abbreviazione intendo una scorciatoia tecnica :

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

21

Secondo questo post di Stephen Cleary, Task.Factory.StartNew () è pericoloso:

Vedo molto codice nei blog e nelle domande SO che utilizzano Task.Factory.StartNew per girare il lavoro su un thread in background. Stephen Toub ha un eccellente articolo di blog che spiega perché Task.Run è meglio di Task.Factory.StartNew, ma penso che molte persone non lo abbiano letto (o non lo capiscano). Quindi, ho preso gli stessi argomenti, ho aggiunto un linguaggio più forte e vedremo come va. :) StartNew offre molte più opzioni di Task.Run, ma è abbastanza pericoloso, come vedremo. Dovresti preferire Task.Run su Task.Factory.StartNuovo in codice asincrono.

Ecco i motivi reali:

  1. Non comprende i delegati asincroni. Questo è in realtà lo stesso del punto 1 per i motivi per cui si desidera utilizzare StartNew. Il problema è che quando si passa un delegato asincrono a StartNew, è naturale presumere che l'attività restituita rappresenti quel delegato. Tuttavia, poiché StartNew non comprende i delegati asincroni, ciò che quell'attività rappresenta effettivamente è solo l'inizio di quel delegato. Questa è una delle prime insidie ​​che i programmatori incontrano quando si utilizza StartNew in codice asincrono.
  2. Pianificatore predefinito confuso. OK, inganna il tempo delle domande: nel codice seguente, su quale thread viene eseguito il metodo "A"?
Task.Factory.StartNew(A);

private static void A() { }

Bene, sai che è una domanda trabocchetto, eh? Se hai risposto "un thread del pool di thread", mi dispiace, ma non è corretto. "A" verrà eseguito su qualunque TaskScheduler sia attualmente in esecuzione!

Ciò significa che potrebbe potenzialmente essere eseguito sul thread dell'interfaccia utente se un'operazione viene completata e torna al thread dell'interfaccia utente a causa di una continuazione, come spiega Stephen Cleary nel suo post.

Nel mio caso, stavo cercando di eseguire attività in background durante il caricamento di un datagrid per una vista e al contempo di visualizzare un'animazione occupata. L'animazione occupata non veniva visualizzata durante l'utilizzo Task.Factory.StartNew()ma l'animazione veniva visualizzata correttamente quando sono passato aTask.Run() .

Per i dettagli, consultare https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html


1

A parte le somiglianze, ovvero Task.Run () che è una scorciatoia per Task.Factory.StartNew (), c'è una piccola differenza tra il loro comportamento in caso di sincronizzazione e delegati asincroni.

Supponiamo che ci siano due metodi seguenti:

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

public int GetInt()
{
    return 1;
}

Ora considera il seguente codice.

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

Qui sia sync1 che sync2 sono di tipo Task <int>

Tuttavia, la differenza arriva nel caso di metodi asincroni.

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

In questo scenario, async1 è di tipo Task <int>, tuttavia async2 è di tipo Task <Task <int>>


Sì, perché Task.Runha incorporato la funzionalità di non wrapping del Unwrapmetodo. Ecco un post sul blog che spiega il ragionamento alla base di questa decisione.
Theodor Zoulias,

-8

Nella mia applicazione che chiama due servizi, ho confrontato sia Task.Run che Task.Factory.StartNew . Ho scoperto che nel mio caso entrambi funzionano bene. Tuttavia, il secondo è più veloce.


Non so perché questa risposta meriti "10" voti negativi, anche se potrebbe non essere corretta o utile ...
Mayer Spitzer
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.