2012-10-19 28 views
23

Dưới đây là một phiên bản đơn giản của mã mà tôi đang gặp sự cố. Khi tôi chạy điều này trong một ứng dụng giao diện điều khiển, nó hoạt động như mong đợi. Tất cả các truy vấn được chạy song song và Task.WaitAll() trả lại khi tất cả đều hoàn tất.Task.WaitAll treo với nhiều tác vụ có thể chờ đợi trong ASP.NET

Tuy nhiên, khi mã này chạy trong ứng dụng web, yêu cầu chỉ bị treo. Khi tôi đính kèm một trình gỡ lỗi và phá vỡ tất cả, nó cho thấy rằng việc thực hiện được chờ đợi trên Task.WaitAll(). Và nhiệm vụ đầu tiên đã hoàn thành, nhưng những công việc khác chưa bao giờ kết thúc.

Tôi không thể hiểu tại sao nó treo khi chạy trong ASP.NET, nhưng hoạt động tốt trong một ứng dụng giao diện điều khiển.

public Foo[] DoWork(int[] values) 
{ 
    int count = values.Length; 
    Task[] tasks = new Task[count]; 

    for (int i = 0; i < count; i++) 
    { 
     tasks[i] = GetFooAsync(values[i]); 
    } 

    try 
    { 
     Task.WaitAll(tasks); 
    } 
    catch (AggregateException) 
    { 
     // Handle exceptions 
    } 

    return ... 
} 

public async Task<Foo> GetFooAsync(int value) 
{ 
    Foo foo = null; 

    Func<Foo, Task> executeCommand = async (command) => 
    { 
     foo = new Foo(); 

     using (SqlDataReader reader = await command.ExecuteReaderAsync()) 
     { 
      ReadFoo(reader, foo); 
     } 
    }; 

    await QueryAsync(executeCommand, value); 

    return foo; 
} 

public async Task QueryAsync(Func<SqlCommand, Task> executeCommand, int value) 
{ 
    using (SqlConnection connection = new SqlConnection(...)) 
    { 
     connection.Open(); 

     using (SqlCommand command = connection.CreateCommand()) 
     { 
      // Set up query... 

      await executeCommand(command); 

      // Log results... 

      return; 
     } 
    }   
} 

Trả lời

46

Thay vì Task.WaitAll bạn cần sử dụng await Task.WhenAll.

Trong ASP.NET, bạn có ngữ cảnh đồng bộ hóa thực tế. Điều này có nghĩa rằng sau khi tất cả các cuộc gọi await bạn sẽ được sắp xếp lại về bối cảnh đó để thực thi việc tiếp tục (thực hiện tuần tự hóa các tiếp nối này một cách hiệu quả). Trong một ứng dụng giao diện điều khiển không có bối cảnh đồng bộ hóa, do đó, tất cả các phần tiếp theo chỉ được gửi đến nhóm luồng. Bằng cách sử dụng Task.WaitAll trong ngữ cảnh của yêu cầu mà bạn đang chặn nó, điều này ngăn cản việc sử dụng nó để xử lý việc tiếp tục từ tất cả các tác vụ khác.

Cũng lưu ý rằng một trong những lợi ích chính của async/await trong ứng dụng ASP là không chặn chuỗi chủ đề mà bạn đang sử dụng để xử lý yêu cầu. Nếu bạn sử dụng Task.WaitAll, bạn đang đánh bại mục đích đó.

Tác dụng phụ của việc thực hiện thay đổi này là bằng cách di chuyển từ hoạt động chặn sang ngoại lệ hoạt động chờ đợi sẽ được truyền bá khác nhau. Thay vì ném AggregateException, nó sẽ ném một trong những ngoại lệ cơ bản.

+1

+1. Mặc dù tôi sẽ sử dụng thuật ngữ "yêu cầu ngữ cảnh" thay vì "chủ đề chính". –

+0

@StephenCleary Vâng, nó đã gây tranh cãi. Tôi đặt chủ đề chính trong dấu ngoặc kép vì nó không phải là chủ đề chính của toàn bộ ứng dụng, nhưng nó có thể được coi là "chủ đề chính của yêu cầu đó". Đó là một cách suy nghĩ hữu ích về vấn đề trong đầu tôi. – Servy

+4

Một cảnh báo quan trọng là nhiệm vụ chờ đợi.WhenAll sẽ không ném AggregateException; chỉ một trong những ngoại lệ bên trong sẽ được truyền bá. Nếu bạn muốn kiểm tra toàn bộ AggregateException, bạn sẽ cần phải lưu trữ một tham chiếu đến nhiệm vụ được trả về từ task.WhenAll và kiểm tra thuộc tính Exception của nó một cách rõ ràng. –

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