จะใช้คุณสมบัติ Can CancelToken ได้อย่างไร?


118

เมื่อเทียบกับรหัสก่อนหน้าสำหรับคลาส RulyCancelerฉันต้องการเรียกใช้โค้ดโดยใช้ CancellationTokenSource.

ฉันจะใช้มันตามที่ระบุไว้ในโทเค็นการยกเลิกได้อย่างไรโดยไม่ต้องโยน / จับข้อยกเว้น ฉันสามารถใช้IsCancellationRequestedทรัพย์สินได้หรือไม่?

ฉันพยายามใช้มันในลักษณะนี้:

cancelToken.ThrowIfCancellationRequested();

และ

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

แต่สิ่งนี้ทำให้เกิดข้อผิดพลาดรันไทม์cancelToken.ThrowIfCancellationRequested();ในวิธีการ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:

รหัสที่ฉันเรียกใช้สำเร็จแล้วจับ OperationCanceledException ในเธรดใหม่:

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/…มีตัวอย่างที่ดีในการใช้CancellationTokenSourceกับวิธี async วิธีการทำงานที่ยาวนานพร้อมการสำรวจความคิดเห็นและการใช้การโทรกลับ
Ehtesh Choudhury

บทความนี้แสดงตัวเลือกที่คุณมีและจำเป็นต้องจัดการโทเค็นตามกรณีเฉพาะของคุณ
Ognyan Dimitrov

คำตอบ:


140

คุณสามารถใช้วิธีการทำงานของคุณได้ดังนี้:

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

แค่นั้นแหละ. คุณต้องจัดการการยกเลิกด้วยตัวเองเสมอ - ออกจากวิธีการเมื่อถึงเวลาที่เหมาะสมในการออก (เพื่อให้งานและข้อมูลของคุณอยู่ในสถานะที่สอดคล้องกัน)

อัปเดต:ฉันไม่ต้องการเขียนwhile (!cancelToken.IsCancellationRequested)เพราะมักจะมีจุดออกไม่กี่จุดที่คุณสามารถหยุดการทำงานได้อย่างปลอดภัยในร่างกายของลูปและลูปมักจะมีเงื่อนไขเชิงตรรกะบางอย่างในการออก (ทำซ้ำทุกรายการในคอลเลกชันเป็นต้น) ดังนั้นฉันเชื่อว่าจะดีกว่าที่จะไม่ผสมเงื่อนไขนั้นเนื่องจากมีเจตนาที่แตกต่าง

ข้อควรระวังเกี่ยวกับการหลีกเลี่ยงCancellationToken.ThrowIfCancellationRequested():

แสดงความคิดเห็นโดยEamon Nerbonne :

... แทนที่ThrowIfCancellationRequestedด้วยเช็คจำนวนมากสำหรับการIsCancellationRequestedออกอย่างสง่างามตามที่คำตอบนี้กล่าว แต่นั่นไม่ใช่แค่รายละเอียดการใช้งาน ที่มีผลต่อพฤติกรรมที่สังเกต: งานจะไม่สิ้นสุดในสถานะถูกยกเลิก RanToCompletionแต่ใน และนั่นสามารถส่งผลกระทบไม่เพียง แต่การตรวจสอบสถานะอย่างชัดเจน แต่ยังรวมถึงการผูกมัดงานด้วยเช่นContinueWithขึ้นอยู่กับการTaskContinuationOptionsใช้งาน ขอบอกว่าการหลีกเลี่ยงThrowIfCancellationRequestedเป็นคำแนะนำที่อันตราย


1
ขอบคุณ! สิ่งนี้ไม่ได้มาจากข้อความออนไลน์ซึ่งค่อนข้างเชื่อถือได้ (หนังสือ "C # 4.0 in a Nutshell"?) ฉันอ้างถึง คุณช่วยอ้างอิงเกี่ยวกับ "always" ได้ไหม
Fulproof

1
สิ่งนี้มาจากการฝึกฝนและประสบการณ์ =) ฉันจำไม่ได้ว่ารู้เรื่องนี้มาจากไหน ฉันใช้ "คุณต้องการเสมอ" เพราะคุณสามารถขัดจังหวะเธรดของผู้ปฏิบัติงานได้โดยมีข้อยกเว้นจากภายนอกโดยใช้ Thread.Abort () แต่นั่นเป็นการปฏิบัติที่แย่มาก อย่างไรก็ตามการใช้ Can CancelToken.ThrowIfCancellationRequested () ยังเป็น "จัดการการยกเลิกด้วยตัวเอง" ซึ่งเป็นวิธีอื่นที่ทำได้
Sasha

1
@OleksandrPshenychnyy ฉันหมายถึงแทนที่ while (true) ด้วย while (! CancelToken.IsCancellationRequested) นี่เป็นประโยชน์! ขอบคุณ!
Doug Dawson

1
@Fulproof ไม่มีวิธีทั่วไปสำหรับรันไทม์ในการยกเลิกการรันโค้ดเนื่องจากรันไทม์ไม่ฉลาดพอที่จะรู้ว่ากระบวนการใดสามารถถูกขัดจังหวะได้ ในบางกรณีมันเป็นไปได้ที่จะออกจากลูปในกรณีอื่น ๆ จำเป็นต้องใช้ตรรกะที่ซับซ้อนมากขึ้นเช่นธุรกรรมต้องย้อนกลับต้องปล่อยทรัพยากร (เช่นการจัดการไฟล์หรือการเชื่อมต่อเครือข่าย) นี่คือเหตุผลที่ไม่มีวิธีวิเศษในการยกเลิกงานโดยไม่ต้องเขียนโค้ด สิ่งที่คุณคิดก็เหมือนกับการฆ่ากระบวนการ แต่นั่นไม่ใช่การยกเลิกนั่นคือหนึ่งในสิ่งที่เลวร้ายที่สุดที่อาจเกิดขึ้นกับแอปพลิเคชันเนื่องจากไม่สามารถล้างข้อมูลได้
user3285954

1
@kosist คุณสามารถใช้ Can CancelToken ไม่มีหากคุณไม่ได้วางแผนที่จะยกเลิกการดำเนินการที่คุณกำลังเริ่มต้นด้วยตนเอง แน่นอนว่าเมื่อกระบวนการของระบบถูกฆ่าทุกอย่างจะถูกขัดจังหวะและ Can CancelToken ก็ไม่มีส่วนเกี่ยวข้องใด ๆ ใช่คุณควรสร้าง Can CancelTokenSource ก็ต่อเมื่อคุณจำเป็นต้องใช้เพื่อยกเลิกการดำเนินการจริงๆ ไม่มีความรู้สึกที่จะสร้างสิ่งที่คุณไม่ได้ใช้
Sasha

26

@ BrainSlugs83

คุณไม่ควรเชื่อถือทุกอย่างที่โพสต์บน stackoverflow แบบสุ่มสี่สุ่มห้า ความคิดเห็นในรหัส Jens ไม่ถูกต้องพารามิเตอร์ไม่ได้ควบคุมว่าจะมีข้อยกเว้นหรือไม่

MSDN ชัดเจนมากว่าพารามิเตอร์ควบคุมอะไรคุณอ่านแล้วหรือยัง? http://msdn.microsoft.com/en-us/library/dd321703(v=vs.110).aspx

หากthrowOnFirstExceptionเป็นจริงข้อยกเว้นจะแพร่กระจายออกจากการเรียกยกเลิกทันทีป้องกันไม่ให้ดำเนินการเรียกกลับที่เหลือและการดำเนินการที่ยกเลิกได้ หาก throwOnFirstExceptionเป็นเท็จการโอเวอร์โหลดนี้จะรวมข้อยกเว้นใด ๆ ที่เกิดขึ้นในการAggregateExceptionโทรกลับหนึ่งครั้งที่มีข้อยกเว้นจะไม่ป้องกันการเรียกกลับที่ลงทะเบียนอื่น ๆ จากการดำเนินการ

ชื่อตัวแปรก็ไม่ถูกต้องเช่นกันเนื่องจากการยกเลิกถูกเรียกโดยCancellationTokenSourceไม่ใช่โทเค็นเองและแหล่งที่มาจะเปลี่ยนสถานะของโทเค็นแต่ละรายการที่จัดการ


ดูเอกสารประกอบ (TAP) ที่นี่เกี่ยวกับการใช้โทเค็นการยกเลิกที่เสนอ: docs.microsoft.com/en-us/dotnet/standard/…
Epstone

1
นี่เป็นข้อมูลที่มีประโยชน์มาก แต่ไม่ตอบคำถามที่ถามเลย
11nallan11

16

คุณสามารถสร้างงานด้วยโทเค็นการยกเลิกเมื่อคุณแอปไปที่พื้นหลังคุณสามารถยกเลิกโทเค็นนี้ได้

คุณสามารถทำได้ใน 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);

โซลูชัน Anther คือ User Timer ใน Xamarin.Forms หยุดจับเวลาเมื่อแอปไปที่พื้นหลัง https://xamarinhelp.com/xamarin-forms-timer/


10

คุณสามารถใช้ThrowIfCancellationRequestedโดยไม่ต้องจัดการข้อยกเว้น!

การใช้ThrowIfCancellationRequestedมีวัตถุประสงค์เพื่อใช้จากภายใน a Task(ไม่ใช่ a Thread) เมื่อใช้ภายใน a Taskคุณไม่จำเป็นต้องจัดการข้อยกเว้นด้วยตัวเอง (และรับข้อผิดพลาด Unhandled Exception) มันจะส่งผลให้ออกไปTaskและTask.IsCancelledคุณสมบัติจะเป็น True ไม่จำเป็นต้องมีการจัดการข้อยกเว้น

ในกรณีเฉพาะของคุณให้เปลี่ยนThreadเป็นTaskไฟล์.

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

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

ทำไมคุณถึงใช้t.Start()และไม่ใช้Task.Run()?
Xander Luciano

1
@XanderLuciano: ในตัวอย่างนี้ไม่มีเหตุผลที่เฉพาะเจาะจงและ Task.Run () น่าจะเป็นทางเลือกที่ดีกว่า
Titus

5

คุณต้องส่งผ่านCancellationTokenไปยังงานซึ่งจะตรวจสอบโทเค็นเป็นระยะเพื่อดูว่ามีการร้องขอการยกเลิกหรือไม่

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(); 

ในกรณีนี้การดำเนินการจะสิ้นสุดเมื่อมีการร้องขอการยกเลิกและTaskจะมีRanToCompletionสถานะ หากคุณต้องการรับทราบว่างานของคุณถูกยกเลิกคุณต้องใช้ThrowIfCancellationRequestedเพื่อโยนOperationCanceledExceptionข้อยกเว้น

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(); 

หวังว่านี่จะช่วยให้เข้าใจดีขึ้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.