2015-04-01 17 views
5

Sau khi đọc Stephen Toub's article on SynchronizationContext tôi là trái với một câu hỏi về đầu ra của mảnh này của .NET 4.5 mã:SynchronizationContext chảy trên Task.Run nhưng không phải trên chờ đợi

private void btnDoSomething_Click() 
{ 
    LogSyncContext("btnDoSomething_Click"); 
    DoItAsync().Wait(); 
} 
private async Task DoItAsync() 
{ 
    LogSyncContext("DoItAsync"); 
    await PerformServiceCall().ConfigureAwait(false); //to avoid deadlocking 
} 
private async Task PerformServiceCall() 
{ 
    LogSyncContext("PerformServiceCall 1"); 
    HttpResponseMessage message = await new HttpClient 
    { 
     BaseAddress = new Uri("http://my-service") 
    } 
    .GetAsync("/").ConfigureAwait(false); //to avoid deadlocking 
    LogSyncContext("PerformServiceCall 2"); 
    await ProcessMessage(message); 
    LogSyncContext("PerformServiceCall 3"); 
} 

private async Task ProcessMessage(HttpResponseMessage message) 
{ 
    LogSyncContext("ProcessMessage"); 
    string data = await message.Content.ReadAsStringAsync(); 
    //do something with data 
} 

private static void LogSyncContext(string statementId) 
{ 
    Trace.WriteLine(String.Format("{0} {1}", statementId, SynchronizationContext.Current != null ? SynchronizationContext.Current.GetType().Name : TaskScheduler.Current.GetType().Name)); 
} 

Đầu ra là:

btnDoSomething_Click WindowsFormsSynchronizationContext

DoItAsync WindowsFormsSynchronizationContext

PerformServiceCall 1 WindowsFormsSynchronizationContext

PerformServiceCall 2 ThreadPoolTaskScheduler

ProcessMessage ThreadPoolTaskScheduler

PerformServiceCall 3 ThreadPoolTaskScheduler

Nhưng tôi mong chờ PerformServiceCall 1 tới không được vào WindowsFormsSynchronizationContext kể từ khi bài báo nói rằng "SynchronizationContext. Hiện tại không "chảy" qua các điểm chờ "...

Bối cảnh không có được thông qua khi gọi PerformServiceCall với Task.Run và một lambda async, như thế này:

await Task.Run(async() => 
{ 
    await PerformServiceCall(); 
}).ConfigureAwait(false); 

bất cứ ai có thể làm rõ hoặc chỉ vào một số tài liệu về vấn đề này?

+1

Cuộc gọi ConfigureAwait() sẽ không có bất kỳ ảnh hưởng nào cho đến khi Tác vụ thực sự bắt đầu chờ. Điều đó đã không xảy ra, cuộc gọi LogSyncContext() của bạn đã đến sớm. Di chuyển nó sau khi chờ đợi. –

+0

Đó không phải là bế tắc trên 'DoItAsync(). Đợi();'? –

+0

Không, nó không phải là bế tắc nhờ vào cuộc gọi ConfigureAwait – Stif

Trả lời

6

Bài viết của Stephen giải thích rằng SynchronizationContext không "chuyển đổi" thành ExecutionContext (mặc dù SynchronizationContext là một phần của ExecutionContext).

ExecutionContext luôn được truyền. Ngay cả khi bạn sử dụng Task.Run vì vậy nếu SynchronizationContext sẽ chảy với nó Task.Run sẽ thực hiện trên chuỗi giao diện người dùng và vì vậy Task.Run sẽ là vô nghĩa. SynchronizationContext không chảy, nó thay vì bị bắt khi một điểm không đồng bộ (tức là await) đạt được và tiếp tục sau khi nó được đăng lên nó (trừ khi có quy định rõ ràng khác).

Sự khác biệt được giải thích trong câu nói này:

Bây giờ, chúng tôi có một quan sát rất quan trọng để thực hiện: chảy ExecutionContext là ngữ nghĩa rất khác so với chụp và post gửi đến SynchronizationContext.

Khi bạn chảy ExecutionContext, bạn đang chụp trạng thái từ một sợi và sau đó khôi phục trạng thái đó sao cho nó xung quanh trong quá trình thực thi của đại biểu được cung cấp. Đó không phải là điều xảy ra khi bạn chụp và sử dụng SynchronizationContext. Phần chụp giống nhau, trong đó bạn đang lấy dữ liệu từ chuỗi hiện tại, nhưng sau đó bạn sử dụng trạng thái đó khác nhau. Thay vì làm cho trạng thái hiện tại trong khi gọi đại biểu, với SynchronizationContext.Post, bạn chỉ cần sử dụng trạng thái đã bắt đó để gọi đại biểu. Nơi và khi nào và làm thế nào đại biểu đó chạy là hoàn toàn lên đến việc thực hiện các phương pháp Post.

Điều đó có nghĩa trong trường hợp của bạn rằng khi bạn ra PerformServiceCall 1 hiện nay SynchronizationContext thực sự là WindowsFormsSynchronizationContext vì bạn chưa đạt bất kỳ điểm nào không đồng bộ và bạn vẫn còn trong thread UI (lưu ý rằng phần trước khi là người đầu tiên await trong phương thức async được thực hiện đồng bộ trên chuỗi gọi để LogSyncContext("PerformServiceCall 1"); xảy ra trước ConfigureAwait(false) xảy ra khi tác vụ được trả lại từ PerformServiceCall).

Bạn chỉ "thoát" giao diện người dùng của SynchronizationContext khi bạn sử dụng ConfigureAwait(false) (không quan tâm đến số bị bắt SynchronizationContext). Lần đầu tiên xảy ra là trên HttpClient.GetAsync và sau đó lại xuất hiện trên PerformServiceCall.

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