2011-09-07 11 views
5

Ecco qualche semplice pezzo di codice per mostrare il comportamento imprevisto:compito interno viene eseguito in un thread inaspettato

public partial class MainWindow : Window 
{ 
    public MainWindow() 
    { 
     InitializeComponent(); 

     _UI = TaskScheduler.FromCurrentSynchronizationContext(); 
     Loaded += new RoutedEventHandler(MainWindow_Loaded); 
    } 

    TaskScheduler _UI; 

    void MainWindow_Loaded(object sender, RoutedEventArgs e) 
    { 
     Task.Factory.StartNew(() => 
     { 
      //Expected: Worker thread 
      //Found: Worker thread 
      DoSomething(); 
     }) 
     .ContinueWith(t => 
      { 
       //Expected: Main thread 
       //Found: Main thread 
       DoSomething(); 

       Task.Factory.StartNew(() => 
       { 
        //Expected: Worker thread 
        //Found: Main thread!!! 
        DoSomething(); 
       }); 
      }, _UI); 
    } 

    void DoSomething() 
    { 
     Debug.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString()); 
    } 
} 

Perché il compito interna eseguita nel thread principale? Come posso evitare questo comportamento?

risposta

6

Sfortunatamente, l'attuale Utilità di pianificazione, quando si sta eseguendo la continuazione, diventa l'installazione dal TaskScheduler.FromCurrentSynchronizationContext.

Questo è discusso in this Connect Bug - ed è stato scritto in questo modo in base alla progettazione in .NET 4. Tuttavia, sono d'accordo che il comportamento lascia un po 'a desiderare qui.

È possibile aggirare questo afferrando uno scheduler "background" nel costruttore, e di utilizzarlo:

TaskScheduler _UI; 

// Store the default scheduler up front 
TaskScheduler _backgroundScheduler = TaskScheduler.Default; 

public MainWindow() 
{ 
    InitializeComponent(); 

    _UI = TaskScheduler.FromCurrentSynchronizationContext(); 
    Loaded += new RoutedEventHandler(MainWindow_Loaded); 
} 

Una volta che avete che, si può facilmente pianificare il vostro compito "background" in modo appropriato:

void MainWindow_Loaded(object sender, RoutedEventArgs e) 
{ 
    Task.Factory.StartNew(() => 
    { 
     //Expected: Worker thread 
     //Found: Worker thread 
     DoSomething(); 
    }) 
    .ContinueWith(t => 
     { 
      //Expected: Main thread 
      //Found: Main thread 
      DoSomething(); 

      // Use the _backgroundScheduler here 
      Task.Factory.StartNew(() => 
      { 
       DoSomething(); 
      }, CancellationToken.None, TaskCreationOptions.None, _backgroundScheduler); 

     }, _UI); 
} 

Inoltre, in questo caso, poiché la tua operazione è alla fine, puoi semplicemente metterla nella sua propria continuazione e ottenere il comportamento che desideri. Questo, tuttavia, non è una soluzione "general purpose", ma funziona in questo caso:

void MainWindow_Loaded(object sender, RoutedEventArgs e) 
{ 
    Task.Factory.StartNew(() => 
    { 
     //Expected: Worker thread 
     //Found: Worker thread 
     DoSomething(); 
    }) 
    .ContinueWith(t => 
     { 
      //Expected: Main thread 
      //Found: Main thread 
      DoSomething(); 

     }, _UI) 
    .ContinueWith(t => 
      { 
       //Expected: Worker thread 
       //Found: Is now worker thread 
       DoSomething(); 
      }); 
} 
+0

Ah fantastico, mi ha aiutato molto. Grazie mille. –

0

Se si vuole garantire che il vostro compito interiore non viene eseguito sul thread dell'interfaccia utente, semplicemente segnare come LongRunning:

Task.Factory.StartNew(() => 
      { 
       //Expected: Worker thread 
       //Found: Main thread!!! 
       DoSomething(); 
      }, TaskCreationOptions.LongRunning); 

Questo dovrebbe garantire che sia dato il proprio thread, invece di inline sul thread corrente .

+0

No SRY, questo non funziona. Ancora eseguito nel thread principale. –

+0

Hai semplicemente provato a non eseguire la continuazione sul thread dell'interfaccia utente e semplicemente inviando eventi al thread dell'interfaccia utente? Cercando di specificare TaskScheduler quando hai solo bisogno di lanciare qualche metodo sulla coda dei thread UI sembra inefficiente. – Tejs

+0

Ho una soluzione - ma non mi piace. Posso fare un SynchronizationContext.Current.Post() prima di eseguire l'attività interna. Ma il punto è - mi aspetto da una chiamata a Task.Factory.StartNew() di essere indipendente dal fatto se li chiamo da un compito esterno o meno. Ho implementato molti metodi con .StartNew() e atm questi metodi si comportano in modo diverso se chiamati o meno all'interno di un'altra attività. –

1

A parte @ canna-copsey`s grande risposta Voglio aggiungere che se si vuole forzare il vostro compito per essere eseguito su un filo di thread è anche possibile utilizzare la proprietà TaskScheduler.Default che si riferisce sempre alla ThreadPoolTaskScheduler:

return Task.Factory.StartNew(() => 
{ 
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString()); 
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); 

in questo modo non dovete catturare l'Utilità di pianificazione in una variabile, come proposto nel @ reed-Copsey La risposta è

Maggiori informazioni sul TaskSchedulers può essere trovato qui: TaskSchedulers on MSDN

Problemi correlati