Görev yapıcısında iptal belirteci: neden?


223

Bazı System.Threading.Tasks.Taskkurucular CancellationTokenparametre olarak a alır:

CancellationTokenSource source = new CancellationTokenSource();
Task t = new Task (/* method */, source.Token);

Bu konuda beni şaşırtan şey , yöntem gövdesinin içinden geçirilen jetona gerçekten ulaşmanın bir yolu olmamasıdır (örneğin, hiçbir şey Task.CurrentTask.CancellationToken). Jeton, durum nesnesi gibi başka bir mekanizma yoluyla sağlanmalı veya bir lambdada yakalanmalıdır.

Peki kurucuda iptal jetonunu sağlamak ne işe yarar?

Yanıtlar:


254

Bir Geçme CancellationTokeniçine Taskyapıcısı görev ile ilişkilendirir.

Stephen Toub'un MSDN'den cevabından alıntı :

Bunun iki temel faydası vardır:

  1. Belirteç Taskyürütülmeye başlamadan önce iptal isterse , Taskyürütülmez. Geçiş yerine Running, hemen geçecektir Canceled. Bu, herhangi bir şekilde çalıştırılırken iptal edilecekse görevi çalıştırma maliyetlerinden kaçınır.
  2. Görevin gövdesi de iptal jetonunu izliyorsa ve OperationCanceledExceptionbu jetonu içeren bir şey atıyorsa (ki bu ne ThrowIfCancellationRequestedyapar), o zaman görev OperationCanceledExceptionbunu gördüğünde , OperationCanceledException'jetonunun Görev jetonuyla eşleşip eşleşmediğini kontrol eder . Eğer öyleyse, bu istisna kooperatif iptali ve devlete (devlet yerine ) Taskgeçişlerin kabulü olarak görülür .CanceledFaulted

2
TPL çok iyi düşünülmüş.
Albay Panik

1
Fayda 1'in bir iptal jetonunu Parallel.Forveya benzeri bir şekilde geçirme iptali için geçerli olduğunu varsayıyorumParallel.ForEach
Albay Panik

27

Yapıcı, iptal işlemini dahili olarak yapmak için belirteci kullanır. Kodunuz jetona erişmek istiyorsa, kodu kendinize iletmekten sorumlusunuz. CodePlex Microsoft .NET ile Paralel Programlama kitap okumak çok tavsiye ediyoruz .

Kitaptan CTS kullanımı örneği:

CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

Task myTask = Task.Factory.StartNew(() =>
{
    for (...)
    {
        token.ThrowIfCancellationRequested();

        // Body of for loop.
    }
}, token);

// ... elsewhere ...
cts.Cancel();

3
jetonu parametre olarak iletmezseniz ne olur? Görünüşe göre davranış aynı olacak, amaç yok.
sergtk

2
@sergdev: jetonu görev ve zamanlayıcıya kaydetmek için geçersiniz. Onu geçmemek ve kullanmak tanımsız bir davranış olurdu.
user7116

3
@sergdev: testten sonra: simgeyi parametre olarak iletmediğinizde myTask.IsCanceled ve myTask.Status aynı değildir. Durum iptal etmek yerine başarısız olacaktır. Bununla birlikte, istisna aynıdır: her iki durumda da bir OperationCanceledException'dır.
Olivier de Rivoyre

2
Aramazsam ne olur token.ThrowIfCancellationRequested();? Testimde davranış aynı. Herhangi bir fikir?
machinarium

1
@CobaltBlue: when cts.Cancel() is called the Task is going to get canceled and end, no matter what you dohayır. Görev başlamadan önce iptal edilirse, İptal edilir . Görevin gövdesi hiçbir zaman hiçbir jetonu kontrol etmezse , tamamlanmaya çalışacak ve RanToCompletion durumuna neden olacaktır. Gövde OperationCancelledExceptionörneğin atarsa , ThrowIfCancellationRequestedGörev, İstisna'nın CancellationToken öğesinin Görev ile ilişkili olanla aynı olup olmadığını kontrol eder. Öyleyse, görev İptal edilir . Değilse, Arızalı .
Wolfzoon

7

Birçoğunun düşünebileceği gibi, iptal işlemi basit bir durum değildir. Bazı incelikler msdn'deki bu blog yazısında açıklanmıştır:

Örneğin:

Paralel Uzantılarda ve diğer sistemlerde belirli durumlarda, bir kullanıcının açıkça iptal etmesinden kaynaklanmayan nedenlerden dolayı engellenmiş bir yöntemi uyandırmak gerekir. Örneğin blockingCollection.Take(), koleksiyonun boş olması ve daha sonra başka bir iş parçacığının çağrılması nedeniyle bir iş parçacığı engellenirse blockingCollection.CompleteAdding(), ilk arama uyanmalı ve InvalidOperationExceptionyanlış bir kullanımı temsil etmek için a atmalıdır.

Paralel Uzantılarda İptal


4

Burada iki noktayı gösteren bir örnektir cevap tarafından Max galkin :

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Start canceled task, don't pass token to constructor");
        Console.WriteLine("*********************************************************************");
        StartCanceledTaskTest(false);
        Console.WriteLine();

        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Start canceled task, pass token to constructor");
        Console.WriteLine("*********************************************************************");
        StartCanceledTaskTest(true);
        Console.WriteLine();

        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Throw if cancellation requested, don't pass token to constructor");
        Console.WriteLine("*********************************************************************");
        ThrowIfCancellationRequestedTest(false);
        Console.WriteLine();

        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Throw if cancellation requested, pass token to constructor");
        Console.WriteLine("*********************************************************************");
        ThrowIfCancellationRequestedTest(true);
        Console.WriteLine();

        Console.WriteLine();
        Console.WriteLine("Test Done!!!");
        Console.ReadKey();
    }

    static void StartCanceledTaskTest(bool passTokenToConstructor)
    {
        Console.WriteLine("Creating task");
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        Task task = null;
        if (passTokenToConstructor)
        {
            task = new Task(() => TaskWork(tokenSource.Token, false), tokenSource.Token);

        }
        else
        {
            task = new Task(() => TaskWork(tokenSource.Token, false));
        }

        Console.WriteLine("Canceling task");
        tokenSource.Cancel();

        try
        {
            Console.WriteLine("Starting task");
            task.Start();
            task.Wait();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex.Message);
            if (ex.InnerException != null)
            {
                Console.WriteLine("InnerException: {0}", ex.InnerException.Message);
            }
        }

        Console.WriteLine("Task.Status: {0}", task.Status);
    }

    static void ThrowIfCancellationRequestedTest(bool passTokenToConstructor)
    {
        Console.WriteLine("Creating task");
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        Task task = null;
        if (passTokenToConstructor)
        {
            task = new Task(() => TaskWork(tokenSource.Token, true), tokenSource.Token);

        }
        else
        {
            task = new Task(() => TaskWork(tokenSource.Token, true));
        }

        try
        {
            Console.WriteLine("Starting task");
            task.Start();
            Thread.Sleep(100);

            Console.WriteLine("Canceling task");
            tokenSource.Cancel();
            task.Wait();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex.Message);
            if (ex.InnerException != null)
            {
                Console.WriteLine("InnerException: {0}", ex.InnerException.Message);
            }
        }

        Console.WriteLine("Task.Status: {0}", task.Status);
    }

    static void TaskWork(CancellationToken token, bool throwException)
    {
        int loopCount = 0;

        while (true)
        {
            loopCount++;
            Console.WriteLine("Task: loop count {0}", loopCount);

            token.WaitHandle.WaitOne(50);
            if (token.IsCancellationRequested)
            {
                Console.WriteLine("Task: cancellation requested");
                if (throwException)
                {
                    token.ThrowIfCancellationRequested();
                }

                break;
            }
        }
    }
}

Çıktı:

*********************************************************************
* Start canceled task, don't pass token to constructor
*********************************************************************
Creating task
Canceling task
Starting task
Task: loop count 1
Task: cancellation requested
Task.Status: RanToCompletion

*********************************************************************
* Start canceled task, pass token to constructor
*********************************************************************
Creating task
Canceling task
Starting task
Exception: Start may not be called on a task that has completed.
Task.Status: Canceled

*********************************************************************
* Throw if cancellation requested, don't pass token to constructor
*********************************************************************
Creating task
Starting task
Task: loop count 1
Task: loop count 2
Canceling task
Task: cancellation requested
Exception: One or more errors occurred.
InnerException: The operation was canceled.
Task.Status: Faulted

*********************************************************************
* Throw if cancellation requested, pass token to constructor
*********************************************************************
Creating task
Starting task
Task: loop count 1
Task: loop count 2
Canceling task
Task: cancellation requested
Exception: One or more errors occurred.
InnerException: A task was canceled.
Task.Status: Canceled


Test Done!!!
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.