2013-04-25 13 views
7

Về đoạn mã này:Tại sao async gọi lại thực hiện trong WPF thread UI

static async Task<string> testc() 
{ 
    Console.WriteLine("helo async " + Thread.CurrentThread.ManagedThreadId); 
    await Task.Run(() => { 
     Thread.Sleep(1000); 
     Console.WriteLine("task " + Thread.CurrentThread.ManagedThreadId); 
    }); 
    Console.WriteLine("callback "+Thread.CurrentThread.ManagedThreadId); 
    return "bob"; 
} 

static void Main(string[] args) 
{ 
    Console.WriteLine("helo sync " + Thread.CurrentThread.ManagedThreadId); 
    testc(); 
    Console.WriteLine("over" + Thread.CurrentThread.ManagedThreadId); 
    Thread.Sleep(2000); 
    Console.ReadLine(); 
} 

tôi nhận được kết quả như sau:

helo sync 10 
helo async 10 
over10 
task 11 
callback **11** 

Đó là OK: đoạn mã sau đang chờ đợi được thực hiện trong cùng một luồng với tư cách là nhiệm vụ.

Bây giờ, nếu tôi làm điều đó trong một ứng dụng WPF:

private void Button_Click_1(object sender, RoutedEventArgs e) 
{ 
    Console.WriteLine("helo sync " + Thread.CurrentThread.ManagedThreadId); 
    testc(); 
    Console.WriteLine("over" + Thread.CurrentThread.ManagedThreadId); 
    Thread.Sleep(2000); 
    Console.ReadLine(); 
} 

nó tạo ra sau đầu ra:

helo sync 8 
helo async 8 
over8 
task 9 
callback **8** 

đâu chúng ta có thể nhìn thấy mã sau khi chờ đợi thực hiện trong thread UI. Vâng, điều này là rất tốt vì nó cho phép thao tác các bộ sưu tập quan sát được ... Nhưng tôi đã tự hỏi "Tại sao?" "Làm thế nào tôi có thể làm như vậy?" Điều này có liên quan đến một số hành vi TaskScheduler không? Điều này có được mã hóa cứng trong .NET Framework không?

Thx cho bất kỳ ý tưởng nào bạn có thể gửi.

Trả lời

7

Lý do là Task.Run sẽ chụp SynchronizationContext nếu có mặt trong ứng dụng WPF khi bắt đầu tác vụ từ chuỗi giao diện người dùng. Nhiệm vụ sau đó sẽ sử dụng SynchronizationContext để sắp xếp lại cuộc gọi lại đến chuỗi giao diện người dùng. Tuy nhiên, nếu không có ngữ cảnh nào khả dụng như trong ứng dụng Console, cuộc gọi lại sẽ xảy ra trên một chuỗi khác.

Stephen Toub đã mô tả điều này trong một số blog entry.

BTW, cẩn thận khi sử dụng không bao giờ sử dụng Thread.Sleep trong Task.Nó có thể gây ra hành vi lạ vì một tác vụ có thể không được gắn với một luồng. Sử dụng Task.Delay để thay thế.

+0

+1 cần nhấn mạnh hơn về BTW của bạn mặc dù. – Phill

2

Nhưng tôi đã tự hỏi "Tại sao?"

Bạn đã trả lời rằng bản thân:

Vâng, điều này là rất tốt vì nó cho phép thao tác bộ sưu tập quan sát vv ...

Toàn bộ các điểm async là làm cho sự không đồng bộ dễ dàng hơn để làm việc với - vì vậy bạn có thể viết mã "đồng bộ hóa" mà thực sự không đồng bộ. Điều đó thường bao gồm việc muốn gắn bó trong một ngữ cảnh (ví dụ: một chuỗi giao diện người dùng) cho toàn bộ phương thức async - chỉ cần "tạm dừng" phương thức (mà không chặn chuỗi giao diện người dùng) khi bạn cần chờ đợi điều gì đó.

"Làm cách nào tôi có thể làm như vậy?"

Không rõ ý bạn là gì ở đây. Về cơ bản, việc triển khai mẫu awaitable cho Task sử dụng TaskScheduler.FromCurrentSynchronizationContext() để tìm ra trình lên lịch để đăng lại cuộc gọi - trừ khi bạn đã gọi ConfigureAwait(false) để chọn không tham gia hành vi này một cách rõ ràng. Vì vậy, đó là cách nó quản lý nó ... có hay không bạn có thể "làm như vậy" phụ thuộc vào chính xác những gì bạn đang cố gắng làm.

Xem câu hỏi "những gì đang chờ đợi" trong async/await FAQ để biết thêm chi tiết về mẫu awaitable.

+0

Bằng cách "Làm cách nào tôi có thể làm như vậy?" Tôi nghĩ rằng ông có nghĩa là: "làm thế nào để có được hành vi WPF mà không cần sử dụng WPF?" –

+0

@ jdv-JandeVaan: Nếu đó là ý nghĩa thì nó không rõ ràng lắm. Dù sao, hy vọng các chi tiết tôi đã đưa ra sẽ cung cấp đủ thông tin để thực hiện thêm OP. Họ luôn có thể hỏi thêm chi tiết nếu cần. –

+0

Ok .. Tốt. Tôi không thực sự muốn làm như vậy. Tôi đã tự hỏi làm thế nào gọi lại được gửi đến phương pháp Dispatcher.Invoke ... và nếu tôi có thể, cuối cùng, làm một điều tương tự hoặc nếu nó đã được mã hóa cứng trong Framework. Thx cho câu trả lời của bạn – Kek

0

Bạn có thể tìm thấy async/await intro hữu ích của mình. Các câu trả lời khác là gần như chính xác.

Khi bạn await a Task chưa hoàn thành, theo mặc định "ngữ cảnh" được chụp được sử dụng để tiếp tục phương thức khi hoàn thành Task. "Ngữ cảnh" này là SynchronizationContext.Currenttrừ khi nó là rỗng, trong trường hợp đó là TaskScheduler.Current.

Lưu ý các điều kiện cần thiết để làm việc này:

  • "Khi bạn await ..." - nếu bạn sắp xếp một sự tiếp nối bằng tay, ví dụ, với Task.ContinueWith, không chụp bối cảnh được thực hiện. Bạn phải tự làm điều đó bằng cách sử dụng một cái gì đó như (SynchronizationContext.Current == null ? TaskSchedler.Current : TaskScheduler.FromCurrentSynchronizationContext()).
  • "... await a Task ..." - hành vi này là một phần của hành vi awaitcho Task loại. Các loại khác có thể hoặc không thể chụp tương tự.
  • "... chưa hoàn thành ..." - nếu Task đã hoàn tất trước thời điểm await ed, phương pháp async sẽ tiếp tục đồng bộ. Vì vậy, không cần chụp ngữ cảnh trong trường hợp đó.
  • "... theo mặc định ..." - đây là hành vi mặc định và có thể thay đổi. Cụ thể, hãy gọi phương thức Task.ConfigureAwait và chuyển false cho thông số continueOnCapturedContext. Phương thức này trả về một kiểu awaitable (không phải là Task) sẽ không tiếp tục trên bối cảnh đã capture nếu tham số của nó là false.
+0

Thx cho câu trả lời hoàn chỉnh này. Hầu hết nó (hoặc ít nhất là đủ để trả lời câu hỏi của tôi) đều nằm trong liên kết trong câu trả lời của @Jakob Christensen, nhưng tôi đề cao quan điểm đầy đủ này! – Kek