2011-09-08 34 views
44

Từ những gì tôi đã đọc về Công việc, mã sau đây sẽ hủy tác vụ hiện đang thực hiện mà không cần ném ngoại lệ. Tôi đã ấn tượng rằng toàn bộ vấn đề hủy bỏ nhiệm vụ là lịch sự "yêu cầu" nhiệm vụ dừng lại mà không hủy bỏ chủ đề.Hủy tác vụ đang ném một ngoại lệ

Kết quả của chương trình sau đây là:

bán phá giá ngoại lệ

[OperationCanceledException]

hủy và trở về thủ tính toán cuối cùng.

Tôi đang cố tránh mọi ngoại lệ khi hủy. Làm thế nào tôi có thể thực hiện điều này?

void Main() 
{ 
    var cancellationToken = new CancellationTokenSource(); 

    var task = new Task<int>(() => { 
     return CalculatePrime(cancellationToken.Token, 10000); 
    }, cancellationToken.Token); 

    try 
    { 
     task.Start(); 
     Thread.Sleep(100); 
     cancellationToken.Cancel(); 
     task.Wait(cancellationToken.Token);   
    } 
    catch (Exception e) 
    { 
     Console.WriteLine("Dumping exception"); 
     e.Dump(); 
    } 
} 

int CalculatePrime(CancellationToken cancelToken, object digits) 
{ 
    int factor; 
    int lastPrime = 0; 

    int c = (int)digits; 

    for (int num = 2; num < c; num++) 
    { 
     bool isprime = true; 
     factor = 0; 

     if (cancelToken.IsCancellationRequested) 
     { 
      Console.WriteLine ("Cancelling and returning last calculated prime."); 
      //cancelToken.ThrowIfCancellationRequested(); 
      return lastPrime; 
     } 

     // see if num is evenly divisible 
     for (int i = 2; i <= num/2; i++) 
     { 
      if ((num % i) == 0) 
      {    
       // num is evenly divisible -- not prime 
       isprime = false; 
       factor = i; 
      } 
     } 

     if (isprime) 
     { 
      lastPrime = num; 
     } 
    } 

    return lastPrime; 
} 
+0

Nếu bạn đang cố gắng tránh trường hợp ngoại lệ, bạn không nên nâng cấp một với ThrowIfCancellationRequested. Chỉ cần trở về duyên dáng từ CalculatePrime. –

+0

Tôi đã xóa cancelToken.ThrowIfCancellationRequested(); và tôi vẫn nhận được kết quả tương tự. –

+0

Không chuyển mã thông báo hủy của bạn sang Task.wait vì bạn đã yêu cầu hủy tác vụ. Gọi Task.wait() để thay thế. –

Trả lời

50

Bạn đang ném một cách rõ ràng một ngoại lệ trên dòng này:

cancelToken.ThrowIfCancellationRequested(); 

Nếu bạn muốn một cách duyên dáng thoát khỏi nhiệm vụ, sau đó bạn chỉ cần để thoát khỏi dòng đó.

Thông thường mọi người sử dụng cơ chế này làm cơ chế kiểm soát để đảm bảo quá trình xử lý hiện tại bị hủy bỏ mà không có khả năng chạy thêm bất kỳ mã nào. Ngoài ra, không có cần phải kiểm tra để hủy bỏ khi gọi ThrowIfCancellationRequested() kể từ khi nó được chức năng tương đương với:

if (token.IsCancellationRequested) 
    throw new OperationCanceledException(token); 

Khi sử dụng ThrowIfCancellationRequested() Nhiệm vụ của bạn có thể trông như thế này:

int CalculatePrime(CancellationToken cancelToken, object digits) { 
    try{ 
     while(true){ 
      cancelToken.ThrowIfCancellationRequested(); 

      //Long operation here... 
     } 
    } 
    finally{ 
     //Do some cleanup 
    } 
} 

Ngoài ra, Task.Wait(CancellationToken) sẽ ném một ngoại lệ nếu mã thông báo bị hủy. Để sử dụng phương pháp này, bạn sẽ cần phải kết thúc cuộc gọi Chờ của bạn trong khối Try...Catch.

MSDN: How to Cancel a Task

+2

Cảm ơn Josh. Có nghĩa là gọi ThrowIfCancellationRequested() trên mỗi lần lặp của vòng lặp thay vì tăng gấp đôi và kiểm tra cờ hủy. –

+17

Như một cảnh báo: thay thế 'ThrowIfCancellationRequested' bằng một loạt các kiểm tra cho 'IsCancellationRequested' thoát một cách duyên dáng, như câu trả lời này nói. Nhưng đó không chỉ là chi tiết triển khai; ảnh hưởng đến hành vi có thể quan sát: tác vụ sẽ không còn kết thúc ở trạng thái đã hủy nữa, nhưng trong 'RanToCompletion'. Và * rằng * có thể ảnh hưởng không chỉ các kiểm tra trạng thái rõ ràng, mà còn ảnh hưởng đến chuỗi nhiệm vụ một cách tinh tế hơn, ví dụ: 'ContinueWith', tùy thuộc vào' TaskContinuationOptions' được sử dụng. Tôi muốn nói rằng tránh 'ThrowIfCancellationRequested' là lời khuyên nguy hiểm. –

+0

"Task.Wait (CancellationToken) sẽ ném một ngoại lệ nếu mã thông báo bị hủy." Đó là vấn đề của tôi. Cám ơn. – granadaCoder

69

Tôi cố gắng để tránh bất kỳ trường hợp ngoại lệ khi hủy.

Bạn không nên làm điều đó.

Ném OperationCanceledException là cách thành ngữ mà "phương pháp bạn gọi là bị hủy" được thể hiện bằng TPL. Đừng chiến đấu chống lại điều đó - chỉ mong đợi nó.

Đây là điều tốt điều này có nghĩa là khi bạn có nhiều thao tác sử dụng cùng mã thông báo hủy, bạn không cần phải tiêu mã ở mọi cấp độ. đã được gọi là đã thực sự hoàn thành bình thường hoặc cho dù nó được trả lại do hủy bỏ. Bạn có thể sử dụng CancellationToken.IsCancellationRequested ở mọi nơi, nhưng nó sẽ làm cho mã của bạn ít thanh lịch hơn trong thời gian dài.

Lưu ý rằng có hai mẩu mã trong ví dụ của bạn được ném một ngoại lệ - một trong nhiệm vụ riêng của mình:

cancelToken.ThrowIfCancellationRequested() 

và một trong những nơi bạn chờ đợi cho nhiệm vụ để hoàn thành:

task.Wait(cancellationToken.Token); 

Tôi không nghĩ bạn thực sự muốn chuyển mã thông báo hủy vào cuộc gọi task.Wait, thành thật mà nói ... cho phép khác mã để hủy chờ đợi của bạn. Do bạn biết bạn vừa hủy mã thông báo đó, nó vô nghĩa - đó là bị ràng buộc để ném ngoại lệ, cho dù tác vụ có thực sự nhận thấy việc hủy hay không. Tùy chọn:

  • Sử dụng một khác nhau thẻ hủy (để mã khác có thể hủy bỏ chờ đợi của bạn một cách độc lập)
  • Sử dụng time-out
  • Chỉ cần chờ đợi cho đến khi nào nó cần
+0

Jon chi phí nhập thử cao hơn nếu (IsCancellationRequested). trong dự án bình thường, bạn sẽ bỏ qua điều đó vì lợi ích của những gì bạn đã nói. tuy nhiên nếu bạn muốn tránh phải trả thêm chi phí bằng bất kỳ chi phí nào thì bạn không thể sử dụng hiệu quả hủy và cần phải xây dựng nó bằng tay ........ Tôi chắc chắn họ có thể đã triển khai bất động sản gì đó ThrowIfCancelled cho những ai muốn ngoại lệ. Nhưng không. –

+8

@Bobb: Lưu ý rằng cả thuộc tính ('IsCancellationRequested') và phương thức (' ThrowIfCancellationRequested') * đều * được triển khai trong TPL. Vì vậy, trong trường hợp * rất cực đoan * nơi bạn thực sự không muốn chi phí của một ngoại lệ, bạn có thể tránh nó - nhưng tôi sẽ không làm như vậy nếu không có * bằng chứng * rằng đó thực sự là một nút cổ chai trong hệ thống. –

+2

Tôi hiện đang gặp vấn đề với điều này, chủ yếu là vì hủy bỏ có vẻ như một hoạt động bình thường, và sử dụng một ngoại lệ cho kiểm soát dòng chảy bình thường * stll * cảm thấy sai. Tôi đang thiếu gì? –

6

Một lưu ý khác về lợi ích của việc sử dụng ThrowIfCancellationRequested thay vì IsCancellationRequested: Tôi nhận thấy rằng khi cần sử dụng ContinueWith với tùy chọn tiếp tục là TaskContinuationOptions.OnlyOnCanceled, IsCancellationRequested sẽ không gây ra điều kiện ContinueWith để kích hoạt. Tuy nhiên, ThrowIfCancellationRequested, sẽ đặt điều kiện Đã hủy của tác vụ, làm cho ContinueWith kích hoạt.

Lưu ý: Điều này chỉ đúng khi tác vụ đang chạy và không phải khi nhiệm vụ bắt đầu. Đây là lý do tại sao tôi đã thêm một Thread.Sleep() giữa bắt đầu và hủy.

CancellationTokenSource cts = new CancellationTokenSource(); 

Task task1 = new Task(() => { 
    while(true){ 
     if(cts.Token.IsCancellationRequested) 
      break; 
    } 
}, cts.Token); 
task1.ContinueWith((ant) => { 
    // Perform task1 post-cancellation logic. 
    // This will NOT fire when calling cst.Cancel(). 
} 

Task task2 = new Task(() => { 
    while(true){ 
     cts.Token.ThrowIfCancellationRequested(); 
    } 
}, cts.Token); 
task2.ContinueWith((ant) => { 
    // Perform task2 post-cancellation logic. 
    // This will fire when calling cst.Cancel(). 
} 

task1.Start(); 
task2.Start(); 
Thread.Sleep(3000); 
cts.Cancel(); 
+0

Đó là vì trạng thái Tác vụ chỉ được đặt thành Hủy khi nó thoát với một loại Ngoại lệ đã cho, nếu không, nếu bạn chỉ "trả lại", trạng thái của nó được đặt thành RanToCompletion. Và do đó, đoạn mã OnlyOnCanceled không được gọi. – almulo

7

Một số câu trả lời ở trên đọc như thể ThrowIfCancellationRequested() sẽ là một tùy chọn. Đó là không phải trong trường hợp này, bởi vì bạn sẽ không nhận được kết quả cuối cùng. idiomatic way that "the method you called was cancelled" được định nghĩa cho các trường hợp khi hủy bỏ nghĩa là vứt bỏ bất kỳ kết quả (trung gian) nào. Nếu định nghĩa hủy của bạn là "ngừng tính toán và trả lại kết quả trung gian cuối cùng" bạn đã rời khỏi đó theo cách đó.

Thảo luận về các lợi ích đặc biệt về mặt thời gian chạy cũng khá gây hiểu lầm: Thuật toán được triển khai sẽ mất khi chạy. Ngay cả việc hủy bỏ tối ưu hóa cao cũng sẽ không làm tốt.

Việc tối ưu hóa dễ nhất sẽ được cuộn vòng lặp này và bỏ qua một số chu kỳ không cần thiết:

for(i=2; i <= num/2; i++) { 
    if((num % i) == 0) { 
    // num is evenly divisible -- not prime 
    isprime = false; 
    factor = i; 
    } 
} 

Bạn có thể

  • tiết kiệm (num/2) -1 chu kỳ cho mỗi số chẵn, đó là ít hơn 50% tổng thể (bỏ đăng ký),
  • lưu (num/2) -square_root_of (num) chu kỳ cho mỗi số nguyên tố (chọn giới hạn theo toán của hệ số nguyên tố nhỏ nhất),
  • tiết kiệm ít nhất là nhiều cho mỗi phi chính, dự kiến ​​tiết kiệm nhiều hơn, ví dụnum = 999 kết thúc với 1 chu kỳ thay vì 499 (ngắt, nếu câu trả lời được tìm thấy) và
  • lưu 50% chu kỳ khác, tất nhiên là 25% (chọn bước theo phép tính số nguyên tố, kiểm soát xử lý trường hợp đặc biệt) 2).

Đó tài khoản để tiết kiệm tối thiểu đảm bảo 75% (ước lượng thô: 90%) của chu kỳ trong vòng lặp bên trong, chỉ bằng cách thay thế nó bằng:

if ((num % 2) == 0) { 
    isprime = false; 
    factor = 2; 
} else { 
    for(i=3; i <= (int)Math.sqrt(num); i+=2) { 
    if((num % i) == 0) { 
     // num is evenly divisible -- not prime 
     isprime = false; 
     factor = i; 
     break; 
    } 
    } 
} 

Có những thuật toán nhanh hơn nhiều (mà Tôi sẽ không thảo luận vì tôi đủ xa chủ đề) nhưng tối ưu hóa này khá dễ dàng và vẫn chứng minh quan điểm của tôi: Đừng lo lắng về thời gian chạy vi tối ưu hóa khi thuật toán của bạn là này cách xa tối ưu.

+0

+1, tuy nhiên tôi không chắc câu trả lời của bạn có công bằng với thuật toán sử dụng chậm như thế nào so với một thuật toán phù hợp hay không. Anh ta nên bắt đầu với số lượng tối đa và làm việc bằng cách sử dụng [kiểm tra tính nguyên thủy của Baillie – PSW] (http://en.wikipedia.org/wiki/Baillie%E2%80%93PSW_primality_test). –

+0

Tôi cho rằng nhiệm vụ trong tay ** không ** giải quyết vấn đề "tính toán lớn nhất có thể trong thời gian tính toán được xác định" nhưng học mã hóa bằng các ví dụ như "tính tất cả số nguyên tố cho đến khi yêu cầu hủy khi trả lại lớn nhất được tính toán chính ". Tôi không muốn bắt đầu một cuộc thảo luận về các thử nghiệm chính ngẫu nhiên, bởi vì các yêu cầu có thể yêu cầu đảm bảo 100% các số nguyên tố được phát hiện. Đối với số nguyên tố lớn, điều này sẽ dẫn đến xét nghiệm nguyên sinh AKS xác định, mà lần lượt sử dụng hoặc bị đánh bại bởi Sàng Eratosthenes cho tất cả các số nhỏ hơn 2^23, nếu tôi nhớ chính xác. –

+2

Xin lỗi, tôi đã đi xa ngoài chủ đề như ban đầu tôi không muốn. Tuy nhiên, điểm mất tích trong câu trả lời của tôi cho "280Z28 15 tháng 1 lúc 1:37" là một tiết kiệm được đảm bảo của 75% chu kỳ bên trong ám bất kỳ "** hủy bỏ ** cuộc thảo luận thời gian chạy **" lỗi thời. Xem xét việc hủy bỏ đó là cho mỗi định nghĩa được thực hiện chỉ một lần duy nhất cho mỗi cuộc gọi, không nên có nhiều ứng dụng mà bạn cần phải lo lắng về thời gian chạy của nó. –

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