โทเค็นการยกเลิกในตัวสร้างงาน: เพราะอะไร


223

ตัวSystem.Threading.Tasks.Taskสร้างบางตัวใช้CancellationTokenเป็นพารามิเตอร์:

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

สิ่งที่ทำให้ฉันงุนงงเกี่ยวกับเรื่องนี้ก็คือไม่มีทางจากภายในร่างกายวิธีการที่จะได้รับจริงผ่านโทเค็น (เช่นไม่มีอะไรเหมือนTask.CurrentTask.CancellationToken) ต้องให้โทเค็นผ่านกลไกอื่น ๆ เช่นวัตถุสถานะหรือถ่ายในแลมบ์ดา

ดังนั้นจุดประสงค์ในการให้โทเค็นการยกเลิกในตัวสร้างใช้เพื่ออะไร

คำตอบ:


254

การส่งผ่านCancellationTokenไปยังตัวTaskสร้างจะเชื่อมโยงกับงาน

การอ้างอิงคำตอบของ Stephen Toub จาก MSDN :

สิ่งนี้มีประโยชน์หลักสองประการ:

  1. หากโทเค็นได้ขอยกเลิกก่อนที่จะTaskเริ่มดำเนินการTaskจะไม่ดำเนินการ แทนที่จะเปลี่ยนไป ก็จะเปลี่ยนไปใช้ทันทีRunning Canceledวิธีนี้จะช่วยหลีกเลี่ยงค่าใช้จ่ายในการทำงานถ้ามันจะถูกยกเลิกในขณะที่ทำงานอยู่
  2. หากเนื้อความของงานตรวจสอบโทเค็นการยกเลิกและส่งOperationCanceledExceptionโทเค็นนั้นที่บรรจุอยู่ (ซึ่งเป็นสิ่งที่ThrowIfCancellationRequestedทำ) จากนั้นเมื่องานเห็นแล้วงานOperationCanceledExceptionจะตรวจสอบว่าOperationCanceledExceptionโทเค็นนั้นตรงกับโทเค็นของงานหรือไม่ หากเป็นเช่นนั้นข้อยกเว้นนั้นจะถูกมองเป็นการยอมรับการยกเลิกแบบมีส่วนร่วมและการTaskเปลี่ยนไปสู่Canceled สถานะ (ไม่ใช่Faultedรัฐ)

2
TPL คิดออกมาดีมาก
พันเอก Panic

1
ฉันถือว่าประโยชน์ที่ 1 นำไปใช้ในทำนองเดียวกันกับการส่งโทเค็นการยกเลิกไปยังParallel.ForหรือParallel.ForEach
Colonel Panic

27

ตัวสร้างใช้โทเค็นสำหรับการจัดการการยกเลิกภายใน หากรหัสของคุณต้องการเข้าถึงโทเค็นคุณต้องรับผิดชอบในการส่งต่อให้ตัวคุณเอง ฉันอยากจะแนะนำการอ่านการเขียนโปรแกรมแบบขนานกับหนังสือ Microsoft .NET ที่ CodePlex

ตัวอย่างการใช้ CTS จากหนังสือ:

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
และจะเกิดอะไรขึ้นหากคุณไม่ผ่านโทเค็นเป็นพารามิเตอร์ ดูเหมือนว่าพฤติกรรมจะเหมือนกันไม่มีจุดประสงค์
sergtk

2
@sergdev: คุณส่งโทเค็นเพื่อลงทะเบียนกับงานและตัวกำหนดตารางเวลา ไม่ผ่านและใช้มันจะเป็นพฤติกรรมที่ไม่ได้กำหนด
user7116

3
@sergdev: หลังจากการทดสอบ: myTask.IsCanceled และ myTask.Status จะไม่เหมือนกันเมื่อคุณไม่ผ่านโทเค็นเป็นพารามิเตอร์ สถานะจะล้มเหลวแทนที่จะถูกยกเลิก อย่างไรก็ตามข้อยกเว้นจะเหมือนกัน: เป็น OperationCanceledException ในทั้งสองกรณี
Olivier de Rivoyre

2
ถ้าฉันไม่ได้เรียกtoken.ThrowIfCancellationRequested();? ในการทดสอบของฉันพฤติกรรมเหมือนกัน ความคิดใด ๆ
machinarium

1
@CobaltBlue: when cts.Cancel() is called the Task is going to get canceled and end, no matter what you doไม่ ถ้างานถูกยกเลิกก่อนที่จะได้เริ่มต้นก็จะถูกยกเลิก หากเนื้อความของงานไม่เคยตรวจสอบโทเค็นใด ๆ มันจะทำงานจนเสร็จซึ่งส่งผลให้สถานะRanToCompletion หากร่างกายโยนOperationCancelledExceptionเช่นโดยThrowIfCancellationRequestedงานจะตรวจสอบว่า CancellationToken ของข้อยกเว้นนั้นเป็นแบบเดียวกับที่เกี่ยวข้องกับงานหรือไม่ ถ้ามันเป็นงานที่มีการยกเลิก ถ้าไม่ได้ก็โทษฐาน
Wolfzoon

7

การยกเลิกไม่ใช่เรื่องง่ายอย่างที่หลายคนคิด รายละเอียดปลีกย่อยบางส่วนได้อธิบายไว้ในโพสต์บล็อกนี้ใน msdn:

ตัวอย่างเช่น:

ในบางสถานการณ์ใน Parallel Extensions และในระบบอื่น ๆ จำเป็นต้องปลุกวิธีการที่ถูกบล็อกด้วยเหตุผลที่ไม่ได้เกิดจากการยกเลิกโดยผู้ใช้อย่างชัดเจน ตัวอย่างเช่นหากหนึ่งเธรดถูกบล็อกblockingCollection.Take()เนื่องจากคอลเลกชันว่างเปล่าและมีเธรดอื่นเรียกใช้ในภายหลัง blockingCollection.CompleteAdding()ดังนั้นการโทรครั้งแรกควรตื่นขึ้นมาและโยนInvalidOperationExceptionเพื่อแสดงการใช้งานที่ไม่ถูกต้อง

การยกเลิกในส่วนขยายแบบขนาน


4

นี่คือตัวอย่างที่แสดงให้เห็นถึงสองจุดในคำตอบของ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;
            }
        }
    }
}

เอาท์พุท:

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