Làm thế nào để sử dụng thuộc tính CancelToken?


117

So với mã trước đó cho lớp RulyCanceler , tôi muốn chạy mã bằng cách sử dụng CancellationTokenSource.

Làm cách nào để sử dụng nó như đã đề cập trong Mã thông báo hủy , tức là không ném / bắt một ngoại lệ? Tôi có thể sử dụng IsCancellationRequestedtài sản không?

Tôi đã cố gắng sử dụng nó như thế này:

cancelToken.ThrowIfCancellationRequested();

try
{
  new Thread(() => Work(cancelSource.Token)).Start();
}
catch (OperationCanceledException)
{
  Console.WriteLine("Canceled!");
}

nhưng điều này gây ra lỗi thời gian chạy cancelToken.ThrowIfCancellationRequested();trong phương thức Work(CancellationToken cancelToken):

System.OperationCanceledException was unhandled
  Message=The operation was canceled.
  Source=mscorlib
  StackTrace:
       at System.Threading.CancellationToken.ThrowIfCancellationRequested()
       at _7CancellationTokens.Token.Work(CancellationToken cancelToken) in C:\xxx\Token.cs:line 33
       at _7CancellationTokens.Token.<>c__DisplayClass1.<Main>b__0() in C:\xxx\Token.cs:line 22
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException:

Mã mà tôi đã chạy thành công bắt được OperationCanceledException trong chuỗi mới:

using System;
using System.Threading;
namespace _7CancellationTokens
{
  internal class Token
  {
    private static void Main()
    {
      var cancelSource = new CancellationTokenSource();
      new Thread(() =>
      {
         try
         {
           Work(cancelSource.Token); //).Start();
         }
         catch (OperationCanceledException)
         {
            Console.WriteLine("Canceled!");
         }
         }).Start();

      Thread.Sleep(1000);
      cancelSource.Cancel(); // Safely cancel worker.
      Console.ReadLine();
    }
    private static void Work(CancellationToken cancelToken)
    {
      while (true)
      {
        Console.Write("345");
        cancelToken.ThrowIfCancellationRequested();
      }
    }
  }
}

2
docs.microsoft.com/en-us/dotnet/standard/threading/… có một số ví dụ khá hay về việc sử dụng CancellationTokenSourcevới các phương thức không đồng bộ, các phương thức chạy dài với thăm dò và sử dụng một cuộc gọi lại.
Ehtesh Choudhury

Bài viết này hiển thị các tùy chọn bạn có và cần xử lý mã thông báo theo trường hợp cụ thể của bạn.
Ognyan Dimitrov

Câu trả lời:


140

Bạn có thể thực hiện phương pháp làm việc của mình như sau:

private static void Work(CancellationToken cancelToken)
{
    while (true)
    {
        if(cancelToken.IsCancellationRequested)
        {
            return;
        }
        Console.Write("345");
    }
}

Đó là nó. Bạn luôn cần tự xử lý việc hủy bỏ - thoát khỏi phương pháp khi đến thời điểm thích hợp để thoát (để công việc và dữ liệu của bạn ở trạng thái nhất quán)

CẬP NHẬT: Tôi không thích viết while (!cancelToken.IsCancellationRequested)vì thường có ít điểm thoát mà bạn có thể dừng thực thi một cách an toàn trên toàn thân vòng lặp và vòng lặp thường có một số điều kiện logic để thoát (lặp qua tất cả các mục trong bộ sưu tập, v.v.). Vì vậy, tôi tin rằng tốt hơn là không nên kết hợp các điều kiện đó vì chúng có ý định khác nhau.

Lưu ý về việc tránh CancellationToken.ThrowIfCancellationRequested():

Nhận xét về câu hỏi của Eamon Nerbonne :

... thay thế ThrowIfCancellationRequestedbằng một loạt các kiểm tra IsCancellationRequestedlối ra một cách duyên dáng, như câu trả lời này nói. Nhưng đó không chỉ là một chi tiết thực hiện; điều đó ảnh hưởng đến hành vi có thể quan sát: nhiệm vụ sẽ không còn kết thúc ở trạng thái bị hủy bỏ nữa, mà ở trong RanToCompletion. Và điều đó có thể ảnh hưởng không chỉ đến việc kiểm tra trạng thái rõ ràng mà còn, tinh vi hơn, chuỗi tác vụ với ví dụ ContinueWith, tùy thuộc vào cách TaskContinuationOptionssử dụng. Tôi muốn nói rằng tránh ThrowIfCancellationRequestedlà lời khuyên nguy hiểm.


1
Cảm ơn! Điều này không tuân theo văn bản trực tuyến, khá có thẩm quyền (cuốn sách "C # 4.0 trong một Nutshell"?) Tôi đã trích dẫn. Bạn có thể cho tôi một tài liệu tham khảo về "always"?
Fulproof

1
Điều này đến từ thực tế và kinh nghiệm =). Tôi không thể nhớ từ đâu tôi biết điều này. Tôi đã sử dụng "bạn luôn cần" bởi vì bạn thực sự có thể ngắt luồng công nhân với ngoại lệ từ bên ngoài bằng cách sử dụng Thread.Abort (), nhưng đó là một thực tế rất tệ. Nhân tiện, sử dụng CancelToken.ThrowIfCancellationRequested () cũng là "tự xử lý việc hủy", chỉ là cách khác để làm điều đó.
Sasha

1
@OleksandrPshenychnyy Ý tôi là thay thế while (true) bằng while (!celToken.IsCancellationRequested). Điều này rất hữu ích! Cảm ơn!
Doug Dawson

1
@Fulproof Không có cách chung nào để thời gian chạy hủy bỏ mã đang chạy bởi vì thời gian chạy không đủ thông minh để biết nơi một quá trình có thể bị gián đoạn. Trong một số trường hợp, có thể chỉ cần thoát khỏi một vòng lặp, trong những trường hợp khác thì cần logic phức tạp hơn, tức là các giao dịch phải được cuộn lại, tài nguyên phải được giải phóng (ví dụ: xử lý tệp hoặc kết nối mạng). Đây là lý do tại sao không có cách kỳ diệu nào để hủy một nhiệm vụ mà không cần phải viết một số mã. Những gì bạn nghĩ giống như giết một quá trình nhưng đó không phải là hủy, đó là một trong những điều tồi tệ nhất có thể xảy ra với một ứng dụng vì không thể dọn dẹp.
user3285954

1
@kosist Bạn có thể sử dụng CancelToken.None nếu bạn không định hủy thao tác đang bắt đầu theo cách thủ công. Tất nhiên khi quá trình hệ thống bị giết, mọi thứ sẽ bị gián đoạn và CancelToken không liên quan gì đến điều đó. Vì vậy, có, bạn chỉ nên tạo CancelTokenSource nếu bạn thực sự cần sử dụng nó để hủy hoạt động. Không có ý nghĩa gì khi tạo ra thứ mà bạn không sử dụng.
Sasha

26

@ BrainSlugs83

Bạn không nên tin tưởng một cách mù quáng mọi thứ được đăng trên stackoverflow. Nhận xét trong mã Jens không chính xác, tham số không kiểm soát việc các ngoại lệ được ném hay không.

MSDN rất rõ thông số đó điều khiển những gì, bạn đã đọc chưa? http://msdn.microsoft.com/en-us/library/dd321703(v=vs.110).aspx

Nếu throwOnFirstExceptionđúng, một ngoại lệ sẽ ngay lập tức lan truyền từ lệnh gọi đến Hủy, ngăn không cho xử lý các lệnh gọi lại còn lại và các hoạt động có thể hủy. Nếu throwOnFirstExceptionlà false, quá tải này sẽ tổng hợp bất kỳ ngoại lệ nào được đưa vào một AggregateException, sao cho một lệnh gọi lại ném một ngoại lệ sẽ không ngăn các lệnh gọi lại đã đăng ký khác được thực thi.

Tên biến cũng sai vì Cancel không được gọi trên CancellationTokenSourcechính mã thông báo và nguồn thay đổi trạng thái của mỗi mã thông báo mà nó quản lý.


Ngoài ra, hãy xem tài liệu (TAP) ở đây về cách sử dụng được đề xuất của mã thông báo hủy: docs.microsoft.com/en-us/dotnet/standard/…
Epstone

1
Đây là thông tin rất hữu ích, nhưng nó không trả lời câu hỏi được đặt ra.
11nallan11

16

Bạn có thể tạo Nhiệm vụ với mã thông báo hủy, khi bạn ứng dụng nền goto, bạn có thể hủy mã này.

Bạn có thể thực hiện việc này trong PCL https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/app-lifecycle

var cancelToken = new CancellationTokenSource();
Task.Factory.StartNew(async () => {
    await Task.Delay(10000);
    // call web API
}, cancelToken.Token);

//this stops the Task:
cancelToken.Cancel(false);

Giải pháp Anther là Hẹn giờ người dùng trong Xamarin.Forms, hẹn giờ dừng khi ứng dụng chạy nền https://xamarinhelp.com/xamarin-forms-timer/


10

Bạn có thể sử dụng ThrowIfCancellationRequestedmà không cần xử lý ngoại lệ!

Việc sử dụng ThrowIfCancellationRequestedcó nghĩa là được sử dụng từ bên trong Task(không phải a Thread). Khi được sử dụng trong a Task, bạn không phải tự mình xử lý ngoại lệ (và gặp lỗi Ngoại lệ không được xử lý). Nó sẽ dẫn đến việc rời khỏi Task, và thuộc Task.IsCancelledtính sẽ là True. Không cần xử lý ngoại lệ.

Trong trường hợp cụ thể của bạn, hãy thay đổi Threadthành a Task.

Task t = null;
try
{
    t = Task.Run(() => Work(cancelSource.Token), cancelSource.Token);
}

if (t.IsCancelled)
{
    Console.WriteLine("Canceled!");
}

Tại sao bạn đang sử dụng t.Start()và không Task.Run()?
Xander Luciano

1
@XanderLuciano: Trong ví dụ này, không có lý do cụ thể nào và Task.Run () sẽ là lựa chọn tốt hơn.
Titus

5

Bạn phải chuyển CancellationTokencho Nhiệm vụ, nhiệm vụ sẽ theo dõi định kỳ mã thông báo để xem liệu việc hủy có được yêu cầu hay không.

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;  
Task task = Task.Run(() => {     
  while(!token.IsCancellationRequested) {
      Console.Write("*");         
      Thread.Sleep(1000);
  }
}, token);
Console.WriteLine("Press enter to stop the task"); 
Console.ReadLine(); 
cancellationTokenSource.Cancel(); 

Trong trường hợp này, hoạt động sẽ kết thúc khi yêu cầu hủy bỏ và di Taskchúc có RanToCompletiontrạng thái. Nếu bạn muốn được công nhận rằng nhiệm vụ của bạn đã bị hủy bỏ , bạn phải sử dụng ThrowIfCancellationRequestedđể ném một OperationCanceledExceptionngoại lệ.

Task task = Task.Run(() =>             
{                 
    while (!token.IsCancellationRequested) {
         Console.Write("*");                      
        Thread.Sleep(1000);                 
    }           
    token.ThrowIfCancellationRequested();               
}, token)
.ContinueWith(t =>
 {
      t.Exception?.Handle(e => true);
      Console.WriteLine("You have canceled the task");
 },TaskContinuationOptions.OnlyOnCanceled);  

Console.WriteLine("Press enter to stop the task");                 
Console.ReadLine();                 
cancellationTokenSource.Cancel();                 
task.Wait(); 

Hy vọng điều này sẽ giúp hiểu rõ hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.