2011-09-07 39 views
5

這裏執行一些簡單的一段代碼,顯示異常行爲:內的任務是在一個意想不到的線程

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

爲什麼內的任務在主線程中執行? 我該如何防止這種行爲?

回答

6

不幸的是,當前的任務計劃程序在運行延續時會成爲您的TaskScheduler.FromCurrentSynchronizationContext設置的SynchronizationContextTaskScheduler

這在this Connect Bug中討論過 - 在.NET 4中用這種方式編寫了設計。但是,我同意這裏的行爲有點不盡人意。

您可以變通的作法是在構造函數抓住一個「背景」的調度,並使用它:

TaskScheduler _UI; 

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

public MainWindow() 
{ 
    InitializeComponent(); 

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

一旦你的,你可以很容易地安排你的「背景」任務適當:

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

此外,在這種情況下,由於您的操作在最後,您可以將它放在自己的延續中並獲得所需的行爲。然而,這不是一個「通用」的解決方案,但在這種情況下工作

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

很酷,這對我很有幫助。非常感謝你。 –

0

如果你想確保你的內心任務不是在UI線程上運行,只需將其標記爲LongRunning

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

這應該確保它被賦予它自己的線程,而不是內聯在當前線程。

+0

沒有SRY基因,這並不工作。 仍在主線程中執行。 –

+0

您是否試過簡單地不運行UI線程上的延續並簡單地將事件分派給UI線程?當你只需要在UI線程隊列中拋出一些方法時,試圖指定TaskScheduler似乎效率低下。 – Tejs

+0

我有一個解決方案 - 但我不喜歡它。在執行內部任務之前,我可以執行SynchronizationContext.Current.Post()。 但重點是 - 我期望從一個調用Task.Factory.StartNew()是獨立的事實,如果我從外部任務調用它們或不。 我使用.StartNew()和atm實現了很多方法,如果在另一個任務中調用或不調用這些方法,它們的行爲會有所不同。 –

1

除了@蘆葦copsey`s偉大答案,我想補充一點,如果要強制你的任務要在一個線程池執行的進程,你還可以使用TaskScheduler.Default屬性總是指ThreadPoolTask​​Scheduler:

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

這樣,你沒有捕捉到一個變量的任務調度像@蘆葦科普塞建議答案是。

上TaskSchedulers的更多信息可以在這裏找到:TaskSchedulers on MSDN

相關問題