2015-09-04 6 views
15

Ho un problema nel codice seguente:Perché Task finisce anche in attesa

static void Main (string[] args) 
{ 
    Task newTask = Task.Factory.StartNew(MainTask); 
    newTask.ContinueWith ((Task someTask) => 
    { 
     Console.WriteLine ("Main State=" + someTask.Status.ToString() + " IsFaulted=" + someTask.IsFaulted+" isComplete="+someTask.IsCompleted); 
    }); 
    while (true) 
    { 

    } 
} 

static async Task MainTask() 
{ 
    Console.WriteLine ("MainStarted!"); 
    Task someTask = Task.Factory.StartNew (() => 
    { 
     Console.WriteLine ("SleepStarted!"); 
     Thread.Sleep(1000); 
     Console.WriteLine ("SleepEnded!"); 
    }); 
    await someTask; 
    Console.WriteLine ("Waiting Ended!!"); 
    throw new Exception ("CustomException!"); 
    Console.WriteLine ("NeverReaches here!!"); 
} 

voglio solo ottenere eccezione da un'attività nuova iniziato MainTask. Ma il risultato non era quello che mi aspettavo.

MainStarted! 
Main State = RanToCompletion IsFaulted = False isComplete = True 
SleepStarted! 
SleepEnded! 
Waiting Ended!! 

Come si può vedere il risultato, l'attività termina prima di "Waiting Ended !!" log della console. Non ho idea del motivo per cui MainTask è terminato anche se in MainTask ha il comando await all'interno? Mi sono perso qualcosa?

+0

Qualsiasi motivo per cui non puoi fare 'MainTask().ContinueWith (...) 'direttamente? –

risposta

18

Task.Factory.StartNewdoes not understand async delegates quindi è necessario utilizzare Task.Run in questo caso e l'eccezione dovrebbe scorrere attraverso.

Task.Factory.StartNew(MainTask); 

è sostanzialmente equivalente a

Task.Factory.StartNew(() => MainTask); 

che ignora il compito tornato da MainTask e l'eccezione viene appena ingoiato.

Vedere questo blog post per ulteriori dettagli.

Provare a utilizzare Task.Run invece e avrai il tuo eccezione:

void Main(string[] args) 
{ 
    Task newTask = Task.Run(MainTask); 
    newTask.ContinueWith((Task someTask) => 
    { 
     Console.WriteLine("Main State=" + someTask.Status.ToString() + " IsFaulted=" + someTask.IsFaulted + " isComplete=" + someTask.IsCompleted); 
    }); 
    while (true) 
    { 

    } 
} 

static async Task MainTask() 
{ 
    Console.WriteLine("MainStarted!"); 
    Task someTask = Task.Run(() => 
    { 
     Console.WriteLine("SleepStarted!"); 
     Thread.Sleep(1000); 
     Console.WriteLine("SleepEnded!"); 
    }); 
    await someTask; 
    Console.WriteLine("Waiting Ended!!"); 
    throw new Exception("CustomException!"); 
    Console.WriteLine("NeverReaches here!!"); 
} 
+0

@MickyDuncan, per riformulare che: "TaskFactory.StartNew non supporta i delegati con riconoscimento async. Task.Run fa." Vedi questo [blog] (http://blog.stephencleary.com/2015/03/a-tour-of-task-part-9-delegate-tasks.html). –

+0

Forse dovrei dire che non capisce delegati asincroni. Vedi http://blog.stephencleary.com/2013/08/startnew-is-dangerous.html –

1

ho modificato il problema ricercato per catturare le eccezioni.

static void Main(string[] args) 
    { 
     DoFoo(); 
     Console.ReadKey(); 
    } 



    static async void DoFoo() 
    { 
     try 
     { 
      await Foo(); 
     } 
     catch (Exception ex) 
     { 
      //This is where you can catch your exception 
     } 
    } 




    static async Task Foo() 
    { 

     await MainTask().ContinueWith((Task someTask) => 
     { 

      Console.WriteLine("Main State=" + someTask.Status.ToString() + " IsFaulted=" + someTask.IsFaulted + " isComplete=" + someTask.IsCompleted); 

     }, TaskContinuationOptions.NotOnFaulted); 

    } 

    static async Task MainTask() 
    { 


     Console.WriteLine("MainStarted!"); 
     Task someTask = Task.Run(() => 
     { 
      Console.WriteLine("SleepStarted!"); 
      Thread.Sleep(1000); 
      Console.WriteLine("SleepEnded!"); 
     }); 
     await someTask; 
     throw new Exception("CustomException!"); 

     Console.WriteLine("Waiting Ended!!"); 


    } 

si dovrebbe usare TaskContinuationOptions.NotOnFaulted che significa che il continuare con compito sarà solo eseguito se l'attività principale non ha avuto alcuna eccezione.

6

Ci sono grandi risposte qui, ma vorrei sottolineare l'ovvio - il Task.Factory.StartNew è completamente ridondante, inutile e usato male.

Se si sostituisce

Task newTask = Task.Factory.StartNew(MainTask); 

con

Task newTask = MainTask(); 

Otterrete esattamente il comportamento che ci si aspetta, senza sprecare un altro filo di thread solamente iniziare un altro thread ThreadPool. In realtà, se si voleva riscrivere il tuo esempio per essere più idiomatica, utilizza qualcosa di simile:

static void Main (string[] args) 
{ 
    var task = 
     MainTask() 
     .ContinueWith(t => Console.WriteLine("Main State={0}", t.Status)); 

    task.Wait(); 
} 

static async Task MainTask() 
{ 
    Console.WriteLine ("MainStarted!"); 

    await Task.Delay(1000); 

    Console.WriteLine ("Waiting Ended!!"); 

    throw new Exception ("CustomException!"); 

    Console.WriteLine ("NeverReaches here!!"); 
} 

Questo codice utilizza solo un filo pool di thread per il codice dopo il ritardo, e genera nuovamente l'eccezione su la chiamata task.Wait() - si potrebbe voler fare qualcos'altro, ovviamente.

Come un lato nota, anche se non si vuole aspettare esplicitamente il compito di completare, non si dovrebbe usare while (true) {} per impedire l'applicazione di terminare - un semplice Console.ReadLine() funzionerà altrettanto bene, e isn spingerai uno dei tuoi core della CPU al 100% di utilizzo :)