Bagaimana cara menggunakan properti CancellationToken?


117

Dibandingkan dengan kode sebelumnya untuk kelas RulyCanceler , saya ingin menjalankan kode menggunakan CancellationTokenSource.

Bagaimana cara saya menggunakannya seperti yang disebutkan dalam Token Pembatalan , yaitu tanpa melempar / menangkap pengecualian? Bisakah saya menggunakan IsCancellationRequestedproperti?

Saya mencoba menggunakannya seperti ini:

cancelToken.ThrowIfCancellationRequested();

dan

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

tetapi ini memberikan kesalahan waktu proses pada cancelToken.ThrowIfCancellationRequested();metode 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:

Kode yang berhasil saya jalankan menangkap OperationCanceledException di utas baru:

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/… memiliki beberapa contoh penggunaan yang cukup bagus CancellationTokenSourcedengan metode async, metode yang berjalan lama dengan polling, dan menggunakan callback.
Ehtesh Choudhury

Artikel ini menunjukkan opsi yang Anda miliki dan perlu menangani token sesuai dengan kasus spesifik Anda.
Ognyan Dimitrov

Jawaban:


140

Anda dapat menerapkan metode kerja Anda sebagai berikut:

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

Itu dia. Anda selalu perlu menangani pembatalan sendiri - keluar dari metode ketika waktu yang tepat untuk keluar (sehingga pekerjaan dan data Anda dalam keadaan konsisten)

PEMBARUAN: Saya lebih suka tidak menulis while (!cancelToken.IsCancellationRequested)karena sering kali ada beberapa titik keluar di mana Anda dapat berhenti mengeksekusi dengan aman di seluruh badan perulangan, dan perulangan biasanya memiliki beberapa kondisi logis untuk keluar (mengulangi semua item dalam koleksi dll.). Jadi saya percaya lebih baik tidak mencampurkan kondisi itu karena mereka memiliki niat yang berbeda.

Catatan peringatan tentang menghindari CancellationToken.ThrowIfCancellationRequested():

Komentar yang dipertanyakan oleh Eamon Nerbonne :

... mengganti ThrowIfCancellationRequesteddengan sekumpulan cek untuk IsCancellationRequestedkeluar dengan anggun, seperti yang dikatakan jawaban ini. Tapi itu bukan hanya detail implementasi; yang memengaruhi perilaku yang dapat diamati: tugas tidak akan lagi berakhir dalam status dibatalkan, tetapi dalam RanToCompletion. Dan itu dapat mempengaruhi tidak hanya pemeriksaan status eksplisit, tetapi juga, lebih halus, rantai tugas misalnya ContinueWith, tergantung pada yang TaskContinuationOptionsdigunakan. Menurut saya, menghindari ThrowIfCancellationRequestedadalah nasihat yang berbahaya.


1
Terima kasih! Ini tidak mengikuti dari teks online, cukup otoritatif (buku "C # 4.0 in a Nutshell"?) Saya kutip. Bisakah Anda memberi saya referensi tentang "selalu"?
Sepenuhnya

1
Ini berasal dari latihan dan pengalaman =). Saya tidak ingat dari mana saya tahu ini. Saya menggunakan "Anda selalu perlu" karena Anda sebenarnya dapat menginterupsi utas pekerja dengan pengecualian dari luar menggunakan Thread.Abort (), tetapi itu praktik yang sangat buruk. Omong-omong, menggunakan CancellationToken.ThrowIfCancellationRequested () juga "menangani pembatalan sendiri", hanya cara lain untuk melakukannya.
Sasha

1
@OleksandrPshenychnyy Maksud saya ganti while (true) dengan while (! CancelToken.IsCancellationRequested). Ini sangat membantu! Terima kasih!
Doug Dawson

1
@Fulproof Tidak ada cara umum untuk runtime untuk membatalkan kode yang sedang berjalan karena runtime tidak cukup pintar untuk mengetahui di mana proses dapat diinterupsi. Dalam beberapa kasus dimungkinkan untuk hanya keluar dari loop, dalam kasus lain logika yang lebih kompleks diperlukan, yaitu transaksi harus dibatalkan, sumber daya harus dilepaskan (misalnya pegangan file atau koneksi jaringan). Inilah sebabnya mengapa tidak ada cara ajaib untuk membatalkan tugas tanpa harus menulis beberapa kode. Apa yang Anda pikirkan adalah seperti menghentikan sebuah proses tetapi itu tidak membatalkan itu adalah salah satu hal terburuk yang dapat terjadi pada aplikasi karena tidak dapat membersihkan.
pengguna3285954

1
@kosist Anda dapat menggunakan CancellationToken.None jika Anda tidak berencana untuk membatalkan operasi yang Anda mulai secara manual. Tentu saja ketika proses sistem dimatikan, semuanya terputus dan CancellationToken tidak ada hubungannya dengan itu. Jadi ya, Anda sebaiknya hanya membuat CancellationTokenSource jika Anda benar-benar perlu menggunakannya untuk membatalkan operasi. Tidak ada gunanya membuat sesuatu yang tidak Anda gunakan.
Sasha

26

@Bayu_joo

Anda tidak boleh begitu saja mempercayai semua yang diposting di stackoverflow. Komentar dalam kode Jens salah, parameter tidak mengontrol apakah pengecualian dilempar atau tidak.

MSDN sangat jelas apa yang mengontrol parameter, apakah Anda sudah membacanya? http://msdn.microsoft.com/en-us/library/dd321703(v=vs.110).aspx

Jika throwOnFirstExceptionbenar, pengecualian akan segera menyebar keluar dari panggilan ke Batal, mencegah panggilan balik yang tersisa dan operasi yang dapat dibatalkan diproses. Jika throwOnFirstExceptionsalah, kelebihan beban ini akan menggabungkan semua pengecualian yang dilemparkan ke dalam sebuah AggregateException, sehingga satu callback yang melontarkan pengecualian tidak akan mencegah panggilan balik terdaftar lainnya untuk dijalankan.

Nama variabel juga salah karena Cancel dipanggil CancellationTokenSourcebukan pada token itu sendiri dan sumber mengubah status setiap token yang dikelolanya.


Lihat juga dokumentasi (TAP) di sini tentang penggunaan yang diusulkan dari token pembatalan: docs.microsoft.com/en-us/dotnet/standard/…
Epstone

1
Ini adalah informasi yang sangat berguna, tetapi tidak menjawab pertanyaan yang diajukan sama sekali.
11nallan11

16

Anda dapat membuat Tugas dengan token pembatalan, saat Anda membuka latar belakang aplikasi, Anda dapat membatalkan token ini.

Anda dapat melakukan ini di 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);

Solusi lain adalah Timer pengguna di Xamarin.Forms, hentikan timer saat aplikasi membuka latar belakang https://xamarinhelp.com/xamarin-forms-timer/


10

Anda dapat menggunakan ThrowIfCancellationRequestedtanpa menangani pengecualian!

Penggunaan ThrowIfCancellationRequesteddimaksudkan untuk digunakan dari dalam a Task(bukan a Thread). Saat digunakan dalam a Task, Anda tidak harus menangani sendiri pengecualian tersebut (dan mendapatkan error Unhandled Exception). Ini akan mengakibatkan meninggalkan Task, dan Task.IsCancelledproperti akan menjadi True. Tidak perlu penanganan terkecuali.

Dalam kasus khusus Anda, ubah Threadmenjadi Task.

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

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

Mengapa Anda menggunakan t.Start()dan tidak Task.Run()?
Xander Luciano

1
@ XanderLuciano: Dalam contoh ini tidak ada alasan khusus, dan Task.Run () akan menjadi pilihan yang lebih baik.
Titus

5

Anda harus meneruskan CancellationTokenke Tugas, yang secara berkala akan memantau token untuk melihat apakah pembatalan diminta.

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

Dalam hal ini, operasi akan berakhir saat pembatalan diminta dan Taskakan memiliki RanToCompletionstatus. Jika Anda ingin diakui bahwa tugas Anda telah dibatalkan , Anda harus menggunakan ThrowIfCancellationRequesteduntuk melemparkan OperationCanceledExceptionpengecualian.

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

Semoga ini membantu untuk memahami lebih baik.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.