2015-12-18 19 views
5

Đoạn mã sau tạo một tác vụ đang bị hủy. Biểu thức await (trường hợp 1) ném System.OperationCanceledException trong khi đồng bộ Wait() (trường hợp 2) ném System.Threading.Tasks.TaskCanceledException (được bọc trong System.AggregateException).OperationCanceledException VS TaskCanceledException khi tác vụ bị hủy

using System; 
using System.Threading; 
using System.Threading.Tasks; 

public class Program 
{ 
    public static void Main() 
    { 
     Program.MainAsync().Wait(); 
    } 

    private static async Task MainAsync() 
    { 
     using(var cancellationTokenSource = new CancellationTokenSource()) 
     { 
      var token = cancellationTokenSource.Token; 
      const int cancelationCheckTimeout = 100; 

      var task = Task.Run(
       async() => 
       { 
        for (var i = 0; i < 100; i++) 
        { 
         token.ThrowIfCancellationRequested(); 
         Console.Write("."); 
         await Task.Delay(cancelationCheckTimeout); 
        } 
       }, 
       cancellationTokenSource.Token 
      ); 

      var cancelationDelay = 10 * cancelationCheckTimeout; 
      cancellationTokenSource.CancelAfter(cancelationDelay); 

      try 
      { 
       await task; // (1) 
       //task.Wait(); // (2) 
      } 
      catch(Exception ex) 
      { 
       Console.WriteLine(ex.ToString()); 
       Console.WriteLine($"Task.IsCanceled: {task.IsCanceled}"); 
       Console.WriteLine($"Task.IsFaulted: {task.IsFaulted}"); 
       Console.WriteLine($"Task.Exception: {((task.Exception == null) ? "null" : task.Exception.ToString())}"); 
      } 
     } 
    } 
} 

Trường hợp 1 đầu ra:

..........System.OperationCanceledException: The operation was canceled. 
    at System.Threading.CancellationToken.ThrowIfCancellationRequested() 
    at Program.<>c__DisplayClass1_0.<<MainAsync>b__0>d.MoveNext() 
--- End of stack trace from previous location where exception was thrown --- 
    at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() 
    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
    at Program.<MainAsync>d__1.MoveNext() 
Task.IsCanceled: True 
Task.IsFaulted: False 
Task.Exception: null 

Trường hợp 2 đầu ra:

..........System.AggregateException: One or more errors occurred. ---> System.Threading.Tasks.TaskCanceledException: A task was canceled. 
    --- End of inner exception stack trace --- 
    at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) 
    at System.Threading.Tasks.Task.Wait() 
    at Program.<MainAsync>d__1.MoveNext() 
---> (Inner Exception #0) System.Threading.Tasks.TaskCanceledException: A task was canceled.<--- 

Task.IsCanceled: True 
Task.IsFaulted: False 
Task.Exception: null 

Tại sao System.AggregateException trong trường hợp thứ 2 không chứa System.OperationCanceledException là một ngoại lệ bên trong?

Tôi biết rằng ThrowIfCancellationRequested() ném OperationCanceledException và chúng tôi có thể thấy rằng trong cả hai trường hợp Task sẽ bị hủy (không bị lỗi).

này câu đố tôi vì hủy một phương pháp từ API .NET tạo ra hành vi phù hợp trong cả hai trường hợp - nhiệm vụ hủy chỉ TaskCanceledException ném:

using System; 
using System.Threading; 
using System.Threading.Tasks; 

public class Program 
{ 
    public static void Main() 
    { 
     Program.MainAsync().Wait(); 
    } 

    private static async Task MainAsync() 
    { 
     using(var cancellationTokenSource = new CancellationTokenSource()) 
     { 
      var token = cancellationTokenSource.Token; 

      var task = Task.Delay(1000, token); 
      cancellationTokenSource.CancelAfter(100); 

      try 
      { 
       await task; // (1) 
       //task.Wait(); // (2) 
      } 
      catch(Exception ex) 
      { 
       Console.WriteLine(ex.ToString()); 
       Console.WriteLine($"Task.IsCanceled: {task.IsCanceled}"); 
       Console.WriteLine($"Task.IsFaulted: {task.IsFaulted}"); 
       Console.WriteLine($"Task.Exception: {((task.Exception == null) ? "null" : task.Exception.ToString())}"); 
      } 
     } 
    } 
} 

Trường hợp 1 đầu ra:

System.Threading.Tasks.TaskCanceledException: A task was canceled. 
    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
    at Program.<MainAsync>d__1.MoveNext() 
Task.IsCanceled: True 
Task.IsFaulted: False 
Task.Exception: null 

Trường hợp 2 đầu ra:

System.AggregateException: One or more errors occurred. ---> System.Threading.Tasks.TaskCanceledException: A task was canceled. 
    --- End of inner exception stack trace --- 
    at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) 
    at System.Threading.Tasks.Task.Wait() 
    at Program.<MainAsync>d__1.MoveNext() 
---> (Inner Exception #0) System.Threading.Tasks.TaskCanceledException: A task was canceled.<--- 

Task.IsCanceled: True 
Task.IsFaulted: False 
Task.Exception: null 
+0

Có quan trọng không? 'TaskCanceledException' là một lớp dẫn xuất của' OperationCanceledException' vì vậy nếu bạn đã nắm bắt 'catch (OperationCanceledException e)', bạn sẽ bắt cả hai loại ngoại lệ. Phần thông tin duy nhất bạn mất là thuộc tính 'TaskCanceledException.Task'. –

+0

Tôi đồng ý nhưng tôi tò mò hơn về lý do giới thiệu/sử dụng 'TaskCanceledException' khi đã có' OperationCanceledException'. –

+1

Lý do để giới thiệu nó là khi bạn có một số trường hợp, bạn có thể lấy bối cảnh của Tác vụ đã bị hủy trong những trường hợp bạn có thể ném biểu mẫu có nguồn gốc hơn bao gồm Tác vụ.'ThrowIfCancellationRequested' được viết chung, nó không biết nó nằm bên trong một Task nên nó làm tăng ngoại lệ tổng quát hơn mà không có thuộc tính Task. –

Trả lời

5

Sự khác biệt ở đây xuất phát từ việc sử dụng token.ThrowIfCancellationRequested(). Phương pháp này kiểm tra việc hủy và nếu được yêu cầu, hãy ném OperationCanceledException cụ thể và không TaskCanceledException (có thể hiểu được là CancellationToken không độc quyền đối với TPL). Bạn có thể nhìn vào reference source và thấy rằng nó gọi phương pháp này:

private void ThrowOperationCanceledException() 
{ 
    throw new OperationCanceledException(Environment.GetResourceString("OperationCanceled"), this); 
} 

"Regular" hủy mặc dù sẽ thực sự tạo ra một TaskCanceledException. Bạn có thể thấy rằng bằng cách hủy token trước nhiệm vụ đã có một cơ hội để bắt đầu chạy:

cancellationTokenSource.Cancel(); 
var task = Task.Run(() => { }, cancellationTokenSource.Token); 
try 
{ 
    await task; 
} 
catch (Exception ex) 
{ 
    Console.WriteLine(ex.ToString()); 
    Console.WriteLine($"Task.IsCanceled: {task.IsCanceled}"); 
    Console.WriteLine($"Task.IsFaulted: {task.IsFaulted}"); 
    Console.WriteLine($"Task.Exception: {((task.Exception == null) ? "null" : task.Exception.ToString())}"); 
} 

Output:

System.Threading.Tasks.TaskCanceledException: A task was canceled. 
    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
    at Sandbox.Program.<MainAsync>d__1.MoveNext() 
Task.IsCanceled: True 
Task.IsFaulted: False 
Task.Exception: null 

phương pháp truyền thống Net thường không sử dụng CancellationToken.ThrowIfCancellationRequested cho async API nó thích hợp khi tải công việc xuống luồng khác. Phương pháp này dành cho các hoạt động không đồng bộ vốn có nên việc hủy bỏ được theo dõi bằng cách sử dụng CancellationToken.Register (hoặc nội bộ InternalRegisterWithoutEC).

+0

Tôi quan sát việc hủy các phương thức .NET (ví dụ 'Task.Delay()' hoặc 'FileStream.WriteAsync()') và nhận thấy rằng tất cả đều ném 'TaskCanceledException' nếu bị hủy bỏ (thông qua CancellationToken không độc quyền với TPL như bạn đã nói) . Nó có ý nghĩa là hủy bỏ Task (không phải gọi lại nó kết thúc tốt đẹp) ném 'TaskCanceledException' như trong ví dụ trên của bạn. Nhưng tại sao 'FileStream.WriteAsync()' không ném 'OperationCanceledException'? Bạn có thể vui lòng mở rộng trên 'công việc offloading đến một thread' lý luận? –

+1

@BojanKomazec 'ThrowIfCancellationRequested' ném' OperationCanceledException'. Bạn chỉ gọi phương thức đó khi bạn có mã chạy mà định kỳ cần kiểm tra để hủy bỏ. API async tích hợp không làm điều đó vì nó đồng bộ. Nó không giữ một thread nào đó trong vòng lặp. Vì vậy, không có mã chạy để gọi 'ThrowIfCancellationRequested'. Ví dụ: – i3arnon

+1

@BojanKomazec 'Task.Delay', tạo nhiệm vụ hứa hẹn, khởi động bộ hẹn giờ sẽ hoàn thành tác vụ khi hết thời gian chờ và đăng ký một đại biểu để hủy tác vụ trên mã thông báo bằng' CancellationToken.Register'. Sau đó nó trả về nhiệm vụ và đó là nó. Không có nơi nào để gọi 'ThrowIfCancellationRequested '. – i3arnon

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