2014-08-31 19 views
10

Tôi đang gặp bế tắc ngay cả sau khi sử dụng ConfigureAwait(false), bên dưới là mã mẫu.bế tắc ngay cả sau khi sử dụng ConfigureAwait (sai) trong dòng Asp.Net

Theo mẫu http://blog.stephencleary.com/2012/02/async-and-await.html (# Ngữ cảnh giải mã), điều này không nên có khóa chết.

Đây là lớp học của tôi:

public class ProjectsRetriever 
{ 
    public string GetProjects() 
    { 
     ... 
     var projects = this.GetProjects(uri).Result; 
     ... 
     ... 
    } 

    private async Task<IEnumerable<Project>> GetProjects(Uri uri) 
    { 
     return await this.projectSystem.GetProjects(uri, Constants.UserName).ConfigureAwait(false); 
    } 
} 

Lớp này là từ một thư viện chia sẻ:

public class ProjectSystem 
{ 
    public async Task<IEnumerable<Project>> GetProjects(Uri uri, string userName) 
    { 
     var projectClient = this.GetHttpClient<ProjectHttpClient>(uri); 
     var projects = await projectClient.GetProjects(); 
     // code here is never hit 
     ... 
} 

trình nếu tôi thêm ConfigureAwait (false) để chờ đợi cuộc gọi trong thư viện chia sẻ , nơi cuộc gọi HttpClient được thực hiện:

public class ProjectSystem 
{ 
    public async Task<IEnumerable<Project>> GetProjects(Uri uri, string userName) 
    { 
     var projectClient = this.GetHttpClient<ProjectHttpClient>(uri); 
     var projects = await projectClient.GetProjects().ConfigureAwait(false); 
     // no deadlock, resumes in a new thread. 
     ... 
} 

Tôi đã đi qua tất cả các blog được tìm thấy, chỉ có sự khác biệt mà tôi thấy là ConfigureAwait (false) hoạt động khi được sử dụng với httpClient.AsyncApi() gọi !?

Hãy giúp làm rõ!

+0

Tiêu đề của bạn nói * "nó không hoạt động ngay cả khi sử dụng' ConfigureAwait (false) 'nhưng trong mã của bạn, bạn nói ví dụ thứ hai hoạt động. –

+0

@Yuval Itzchakov: nó không hoạt động khi được sử dụng trong 'ProjectsRetriever' và hoạt động khi được sử dụng trong' ProjectSystem' –

+1

@ user2746890: 'Tôi đã được giả định, một khi ConfigureAwait (false) được sử dụng (bất kỳ nơi nào trong ngăn xếp cuộc gọi), thực thi từ thời điểm đó sẽ không gây ra deadlock.' Nó sẽ không nắm bắt bối cảnh * cho rằng chờ đợi *. Nhưng phá vỡ các yêu cầu và chờ đợi của bạn, và bạn sẽ thấy rằng 'ProjectSystem.GetProjects' được gọi (và đang chờ) * trước * bạn gọi' ConfigureAwait (false) 'trên tác vụ được trả về bởi' GetProjects'. IMO câu trả lời hay nhất là "chỉ cung cấp một API không đồng bộ", tức là, làm cho 'ProjectsRetriever.GetProjects()' không đồng bộ. –

Trả lời

14

Từ nhận xét:

Tôi đã theo giả định, một khi ConfigureAwait (false) được sử dụng (bất kỳ nơi nào trong ngăn xếp cuộc gọi), e xecution từ điểm đó sẽ không gây ra bế tắc.

Tôi không tin vào ma thuật đen, và bạn cũng không nên. Luôn cố gắng hiểu điều gì xảy ra khi bạn sử dụng thứ gì đó trong mã của mình.

Khi bạn await một phương pháp async mà trả về một Task hoặc một Task<T>, có một nắm bắt tiềm ẩn của SynchronizationContext bởi TaskAwaitable được tạo ra theo phương pháp Task.GetAwaiter.

Khi đã bối cảnh đồng bộ được đặt ra và phương thức gọi async hoàn tất, TaskAwaitable cố gắng sắp xếp việc tiếp tục (mà về cơ bản là phần còn lại của phương pháp này gọi sau từ khóa đầu tiên await) vào SynchronizationContext (sử dụng SynchronizationContext.Post) được đã chụp trước đó. Nếu chuỗi cuộc gọi bị chặn, hãy chờ trên cùng phương thức đó để hoàn tất, bạn có một khóa chết .

Bạn nên tự hỏi mình Should I expose synchronous wrappers for asynchronous methods? 99 phần trăm thời gian câu trả lời là không. Bạn nên sử dụng API đồng bộ, chẳng hạn như một ưu đãi WebClient.

+0

Tôi đã tạo một thư viện có thể dễ dàng cắm vào ứng dụng ASP.NET của bạn để phát hiện các khóa chết này và giúp bạn theo dõi chúng. Thông tin thêm về https://github.com/ramondeklein/deadlockdetection. –

+0

Tôi có thể cảm thấy sự tự tin của bạn bằng cách đọc câu trả lời của bạn. cũng trả lời. – johni

6

Nó khối khi sử dụng trong ProjectsRetriever vì:

public class ProjectsRetriever 
{ 
    public string GetProjects() 
    { 
     //querying the result blocks the thread and wait for result. 
     var projects = this.GetProjects(uri).Result; 
     ... //require Thread1 to continue. 
     ... 
    } 

    private async Task<IEnumerable<Project>> GetProjects(Uri uri) 
    { 
     //any thread can continue the method to return result because we use ConfigureAwait(false) 
     return await this.projectSystem.GetProjects(uri, Constants.UserName).ConfigureAwait(false); 
    } 
} 

public class ProjectSystem 
{ 
    public async Task<IEnumerable<Project>> GetProjects(Uri uri, string userName) 
    { 
     var projectClient = this.GetHttpClient<ProjectHttpClient>(uri); 
     var projects = await projectClient.GetProjects(); 
     // code here is never hit because it requires Thread1 to continue its execution 
     // but Thread1 is blocked in var projects = this.GetProjects(uri).Result; 
     ... 
} 

Nó không chặn khi sử dụng trong ProjectSystem vì:

public class ProjectsRetriever 
{ 
    public string GetProjects() 
    { 
     ... 
     var projects = this.GetProjects(uri).Result; 
     ...//requires Thread1 to continue 
     ... 
    } 

    private async Task<IEnumerable<Project>> GetProjects(Uri uri) 
    { 
     //requires Thread1 to continue 
     return await this.projectSystem.GetProjects(uri, Constants.UserName); 
    } 
} 

public class ProjectSystem 
{ 
    public async Task<IEnumerable<Project>> GetProjects(Uri uri, string userName) 
    { 
     var projectClient = this.GetHttpClient<ProjectHttpClient>(uri); 
     var projects = await projectClient.GetProjects().ConfigureAwait(false); 
     // no deadlock, resumes in a new thread. After this function returns, Thread1 could continue to run 
} 
+0

xin lỗi, đó là phần gây nhầm lẫn cho tôi. Có phải vì HttpClient đang thực hiện cuộc gọi nghỉ ngơi (thực hiện được chuyển giao, không xảy ra trong cùng một bối cảnh)? –

+0

@ user2746890: phần nào làm bạn bối rối? Trong ví dụ đầu tiên, bạn gọi 'await projectClient.GetProjects()' với Thread1, vì vậy Thread1 phải tiếp tục thực thi nhưng nó được BẬT trong 'this.GetProjects (uri) .Result;' –

+0

Tôi đã được giả thiết, một khi ConfigureAwait (false) được sử dụng (bất kỳ nơi nào trong ngăn xếp cuộc gọi), việc thực hiện từ điểm đó sẽ không gây ra bế tắc. –

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