2014-05-20 17 views
8

tôi sử dụng một tập hợp các nhiệm vụ vào những thời điểm, và để chắc chắn rằng họ đều chờ đợi tôi sử dụng phương pháp này:Tại sao ngoại lệ này không bị ném?

public async Task ReleaseAsync(params Task[] TaskArray) 
{ 
    var tasks = new HashSet<Task>(TaskArray); 
    while (tasks.Any()) tasks.Remove(await Task.WhenAny(tasks)); 
} 

và sau đó gọi nó như thế này:

await ReleaseAsync(task1, task2, task3); 
//or 
await ReleaseAsync(tasks.ToArray()); 

Tuy nhiên, thời gian gần đây Tôi đã nhận thấy một số hành vi lạ và được đặt để xem có sự cố với phương thức ReleaseAsync hay không. Tôi quản lý để thu hẹp nó xuống bản demo đơn giản này, nó chạy trong linqpad nếu bạn bao gồm System.Threading.Tasks. Nó cũng sẽ làm việc một chút sửa đổi trong một ứng dụng giao diện điều khiển hoặc trong một bộ điều khiển MVC asp.net.

async void Main() 
{ 
Task[] TaskArray = new Task[]{run()}; 
var tasks = new HashSet<Task>(TaskArray); 
while (tasks.Any<Task>()) tasks.Remove(await Task.WhenAny(tasks)); 
} 

public async Task<int> run() 
{ 
return await Task.Run(() => { 
    Console.WriteLine("started"); 
    throw new Exception("broke"); 
    Console.WriteLine("complete"); 
    return 5; 
}); 
} 

Điều tôi không hiểu là lý do ngoại lệ không bao giờ xuất hiện ở bất kỳ đâu. Tôi đã nghĩ rằng nếu các Nhiệm vụ với ngoại lệ được chờ đợi, nó sẽ ném đi. Tôi đã có thể khẳng định điều này bằng cách thay thế các vòng lặp trong khi với một đơn giản đối với từng như thế này:

foreach(var task in TaskArray) 
{ 
    await task;//this will throw the exception properly 
} 

Câu hỏi của tôi là, tại sao không phải là ví dụ thể hiện ném ngoại lệ đúng cách (nó không bao giờ xuất hiện bất cứ nơi nào).

+0

thể trùng lặp của (http://stackoverflow.com/questions/22856052/how- to-handle-task-factory-startnew-exception) –

+0

Bất kỳ lý do nào để không sử dụng 'Task.WhenAll'? –

+0

@PauloMorgado - Vâng, chúng được gắn với tài nguyên được quản lý và tôi muốn phát hành chúng khi chúng có sẵn thay vì chờ đợi cho tất cả chúng hoàn thành và sau đó phát hành. –

Trả lời

9

TL; DR: run() ném ngoại lệ, nhưng bạn đang chờ WhenAny(), mà không ném một ngoại lệ riêng của mình.


Các tài liệu MSDN cho WhenAny trạng thái:

Nhiệm vụ trở lại sẽ hoàn thành khi một trong các nhiệm vụ cung cấp đã hoàn thành. Tác vụ được trả về sẽ luôn kết thúc ở trạng thái RanToCompletion với tập kết quả của nó là nhiệm vụ đầu tiên cần hoàn thành. Điều này đúng ngay cả khi nhiệm vụ đầu tiên hoàn thành đã kết thúc ở trạng thái Bị hủy hoặc Bị lỗi.

Về cơ bản những gì đang xảy ra là nhiệm vụ được trả về bởi WhenAny chỉ đơn giản là nuốt nhiệm vụ bị lỗi. Nó chỉ quan tâm đến thực tế là nhiệm vụ được hoàn thành, không phải là nó đã hoàn thành thành công. Khi bạn chờ đợi nhiệm vụ, nó chỉ đơn giản là hoàn thành mà không có lỗi, bởi vì nó là nhiệm vụ nội bộ đã bị lỗi, và không phải là một trong những bạn đang chờ đợi.

10

Một Task không được awaited hoặc không sử dụng Wait() hoặc Result() phương pháp của nó, sẽ nuốt trừ theo mặc định. Hành vi này có thể được sửa đổi lại theo cách nó đã được thực hiện trong .NET 4.0 bằng cách đâm quá trình chạy một khi Task là GC'd. Bạn có thể đặt nó trong app.config của bạn như sau:

<configuration> 
    <runtime> 
     <ThrowUnobservedTaskExceptions enabled="true"/> 
    </runtime> 
</configuration> 

Một trích dẫn từ bài viết trên blog this bởi nhóm lập trình song song trong Microsoft:

Những người bạn quen thuộc với Nhiệm vụ trong .NET 4 sẽ biết rằng TPL có khái niệm về ngoại lệ “không được quan sát”. Đây là một thỏa hiệp giữa hai mục tiêu thiết kế cạnh tranh trong TPL: hỗ trợ marshaling ngoại lệ unhandled từ các hoạt động không đồng bộ với mã tiêu thụ hoàn thành/đầu ra của nó, và tuân theo các chính sách leo thang ngoại lệ tiêu chuẩn .NET cho trường hợp ngoại lệ không được xử lý bởi mã của ứng dụng. Kể từ .NET 2.0, các ngoại lệ không được giải quyết trên các luồng mới được tạo ra, trong các mục công việc ThreadPool và tất cả kết quả giống như trong hành vi leo thang ngoại lệ mặc định, đó là cho quá trình sụp đổ. Điều này thường được mong muốn, vì các trường hợp ngoại lệ cho thấy đã xảy ra sự cố và sự cố giúp nhà phát triển xác định ngay rằng ứng dụng đã nhập một trạng thái không đáng tin cậy. Lý tưởng nhất, nhiệm vụ sẽ theo hành vi tương tự này. Tuy nhiên, các tác vụ được sử dụng để biểu diễn các hoạt động không đồng bộ với mã sau đó tham gia và nếu các hoạt động không đồng bộ đó xảy ra ngoại lệ, các ngoại lệ đó phải được so khớp với nơi mã nối đang chạy và tiêu thụ kết quả của hoạt động không đồng bộ. Điều đó vốn có nghĩa là TPL cần phải dừng lại những trường hợp ngoại lệ này và giữ chúng cho đến khi chúng có thể được ném trở lại khi mã tiêu thụ truy cập vào nhiệm vụ. Vì điều đó ngăn cản chính sách leo thang mặc định, .NET 4 đã áp dụng khái niệm ngoại lệ “không được quan sát” để bổ sung khái niệm ngoại lệ “chưa được giải quyết”. Ngoại lệ “không được giám sát” là ngoại lệ được lưu trữ trong tác vụ nhưng sau đó không bao giờ được xem xét theo bất kỳ cách nào bằng mã tiêu thụ. Có nhiều cách để quan sát ngoại lệ, bao gồm Wait() 'ing vào Task, truy cập vào Kết quả của tác vụ, xem xét thuộc tính Ngoại lệ của Tác vụ, v.v. Nếu mã không bao giờ quan sát ngoại lệ của Tác vụ, thì khi Tác vụ biến mất, TaskScheduler.UnobservedTaskException được nâng lên, tạo cho ứng dụng thêm một cơ hội để “quan sát” ngoại lệ. Và nếu ngoại lệ vẫn không được giám sát, chính sách leo thang ngoại lệ sau đó được kích hoạt bởi ngoại lệ sẽ không được giải quyết trên luồng kết thúc.

+0

Tôi tin câu trả lời của Yuval giải thích hành vi bạn đang thấy. Ngoài ra, có thể truy cập ngoại lệ trong đối tượng nhiệm vụ chạy() trả về. – Naylor

4

Từ những nhận xét:

những [nhiệm vụ] đã được gắn với nguồn lực quản lý và tôi muốn phát hành chúng khi họ trở nên có sẵn thay vì chờ cho tất cả trong số họ để hoàn thành và sau đó phát hành.

Sử dụng một phương pháp helper async void có thể cung cấp cho bạn những hành vi mong muốn cho cả hai loại bỏ các nhiệm vụ đã hoàn thành từ danh sách và ngay lập tức ném ngoại lệ không quan sát được:

public static class TaskExt 
{ 
    public static async void Observe<TResult>(Task<TResult> task) 
    { 
     await task; 
    } 

    public static async Task<TResult> WithObservation(Task<TResult> task) 
    { 
     try 
     { 
      return await task; 
     } 
     catch (Exception ex) 
     { 
      // Handle ex 
      // ... 

      // Or, observe and re-throw 
      task.Observe(); // do this if you want to throw immediately 

      throw; 
     } 
    } 
} 

Sau đó, mã của bạn có thể trông như thế này (chưa được kiểm tra):

async void Main() 
{ 
    Task[] TaskArray = new Task[] { run().WithObservation() }; 
    var tasks = new HashSet<Task>(TaskArray); 
    while (tasks.Any<Task>()) tasks.Remove(await Task.WhenAny(tasks)); 
} 

.Observe() sẽ tái ném ngoại lệ của công việc ngay lập tức "out-of-band", sử dụng SynchronizationContext.Post nếu tiểu trình đang gọi có một đồng bộ ngữ cảnh nization hoặc sử dụng ThreadPool.QueueUserWorkItem nếu không. Bạn có thể xử lý các ngoại lệ "ngoài băng" như vậy với AppDomain.CurrentDomain.UnhandledException).

tôi đã mô tả này chi tiết hơn tại đây: [? Làm thế nào để xử lý ngoại lệ Task.Factory.StartNew]

TAP global exception handler

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