2011-09-07 24 views
5

Dưới đây là một số mảnh dễ dàng mã để hiển thị các hành vi bất ngờ:nhiệm vụ Nội được thực hiện trong một chủ đề bất ngờ

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

Tại sao nhiệm vụ bên trong thực hiện trong các chủ đề chính? Làm cách nào để ngăn chặn hành vi này?

Trả lời

6

Rất tiếc, Trình lập lịch tác vụ hiện tại, khi bạn đang chạy tiếp tục, trở thành thiết lập SynchronizationContextTaskScheduler theo số TaskScheduler.FromCurrentSynchronizationContext của bạn.

Điều này được thảo luận trong this Connect Bug - và được viết theo cách này theo thiết kế trong .NET 4. Tuy nhiên, tôi đồng ý rằng hành vi này để lại một chút mong muốn ở đây.

Bạn có thể làm việc này bằng cách lấy một "nền" lên lịch trong constructor của bạn, và sử dụng nó:

TaskScheduler _UI; 

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

public MainWindow() 
{ 
    InitializeComponent(); 

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

Một khi bạn có đó, bạn có thể dễ dàng lên lịch "nền" của bạn nhiệm vụ một cách thích hợp:

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

Ngoài ra, trong trường hợp này, kể từ khi hoạt động của bạn ở cuối, bạn chỉ có thể đặt nó trong sự tiếp tục của riêng mình và nhận được hành vi mà bạn muốn. Này, tuy nhiên, không phải là một "mục đích chung" giải pháp, nhưng hoạt động trong trường hợp này:

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 tuyệt vời, điều đó đã giúp tôi rất nhiều. Cảm ơn nhiều. –

0

Nếu bạn muốn đảm bảo rằng công việc bên trong của bạn không chạy trên thread UI, bạn chỉ cần đánh dấu nó như LongRunning:

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

Điều đó sẽ đảm bảo nó được đưa ra chủ đề của riêng mình thay vì nội tuyến trên thread hiện tại .

+0

Không có lỗi, công việc này không hoạt động. Vẫn được thực hiện trong chuỗi chính. –

+0

Bạn đã thử chỉ đơn giản là không chạy liên tục trên chuỗi giao diện người dùng và chỉ cần gửi các sự kiện đến chuỗi giao diện người dùng? Cố gắng chỉ định TaskScheduler khi bạn chỉ cần ném một số phương thức trên hàng đợi của chuỗi giao diện người dùng có vẻ không hiệu quả. – Tejs

+0

Tôi có một giải pháp - nhưng tôi không thích nó. Tôi có thể làm một SynchronizationContext.Current.Post() trước khi thực hiện nhiệm vụ bên trong. Nhưng vấn đề là - tôi mong đợi từ một cuộc gọi đến Task.Factory.StartNew() để được độc lập của thực tế nếu tôi gọi họ từ một nhiệm vụ bên ngoài hay không. Tôi đã triển khai rất nhiều phương thức với .StartNew() và atm các phương thức này hoạt động khác nếu được gọi hay không từ bên trong một tác vụ khác. –

1

Ngoài @ sậy-copsey`s câu trả lời tuyệt vời, tôi muốn thêm rằng nếu bạn muốn để buộc nhiệm vụ của bạn được thực hiện trên một threadpool sợi bạn cũng có thể sử dụng thuộc tính TaskScheduler.Default mà luôn luôn đề cập đến ThreadPoolTaskScheduler:

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

Bằng cách này bạn không cần phải nắm bắt được lịch trình công việc trong một biến như đề xuất trong @ sậy-copsey `s trả lời.

Thông tin thêm về TaskSchedulers có thể tìm thấy tại đây: TaskSchedulers on MSDN

Các vấn đề liên quan