2011-09-07 22 views
5

Aquí hay alguna pieza fácil de código para mostrar el comportamiento inesperado:tarea interior se ejecuta en un hilo inesperado

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

¿Por qué es la tarea interna ejecutados en el hilo principal? ¿Cómo puedo prevenir este comportamiento?

Respuesta

6

Desafortunadamente, el programador de tareas actual, cuando está ejecutando su continuación, se convierte en la configuración SynchronizationContextTaskScheduler por su TaskScheduler.FromCurrentSynchronizationContext.

Esto se discute en this Connect Bug - y fue escrito esta manera por diseño en .NET 4. Sin embargo, estoy de acuerdo que el comportamiento deja un poco que desear aquí.

Puede solucionar este por el acaparamiento de un programador de "fondo" en su constructor, y su uso:

TaskScheduler _UI; 

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

public MainWindow() 
{ 
    InitializeComponent(); 

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

Una vez que tenga eso, se puede programar fácilmente su tarea "fondo" apropiada:

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

Además, en este caso, dado que su operación se encuentra al final, puede simplemente ponerla en su propia continuación y obtener el comportamiento que desea. Esto, sin embargo, no es una solución "propósito general", pero funciona en este 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, genial, eso me ayudó mucho. Muchas gracias. –

0

Si desea asegurarse de que su tarea interior no se ejecuta en el subproceso de interfaz de usuario, sólo tiene que marcarlo como LongRunning:

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

que deben garantizar que se le da su propio hilo en lugar de procesos en línea en el hilo actual .

+0

Sin SRY, esto no funciona. Ejecutado en el hilo principal. –

+0

¿Ha intentado simplemente no ejecutar la continuación en el hilo de la interfaz de usuario y simplemente enviar eventos al hilo de la interfaz de usuario? Tratar de especificar TaskScheduler cuando solo tiene que arrojar algún método en la cola de subprocesos de UI parece ineficaz. – Tejs

+0

Tengo una solución, pero no me gusta. Puedo hacer un SynchronizationContext.Current.Post() antes de ejecutar la tarea interna. Pero el punto es - espero que de una llamada a Task.Factory.StartNew() sea independiente del hecho si los llamo desde una tarea externa o no. Implementé muchos métodos con .StartNew() y ATM estos métodos se comportan diferente si se llama o no desde dentro de otra tarea. –

1

@ Aparte de juncos copsey`s gran respuesta Quiero añadir que si se quiere forzar a su tarea para ser ejecutado en un threadpool hilo también se puede utilizar la propiedad TaskScheduler.Default que siempre se refiere a la ThreadPoolTaskScheduler:

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

de esta manera usted no tiene que capturar el programador de tareas en una variable, como se propone en el @ Reed-Copsey la respuesta de.

Más información sobre TaskSchedulers se puede encontrar aquí: TaskSchedulers on MSDN

Cuestiones relacionadas