ฉันจะยกเลิก / ยกเลิกภารกิจของ TPL ได้อย่างไร


156

ในหัวข้อฉันสร้างบางอย่าง System.Threading.Taskและเริ่มต้นแต่ละงาน

เมื่อฉันทำ.Abort()เพื่อฆ่าเธรดงานจะไม่ถูกยกเลิก

ฉันจะส่ง.Abort()ไปยังงานของฉันได้อย่างไร


คำตอบ:


228

คุณทำไม่ได้ งานใช้เธรดพื้นหลังจากกลุ่มเธรด นอกจากนี้ไม่แนะนำให้ยกเลิกการใช้เธรดด้วยวิธีการยกเลิก คุณสามารถดูโพสต์บล็อกต่อไปนี้ซึ่งอธิบายวิธีที่เหมาะสมในการยกเลิกงานโดยใช้โทเค็นการยกเลิก นี่คือตัวอย่าง:

class Program
{
    static void Main()
    {
        var ts = new CancellationTokenSource();
        CancellationToken ct = ts.Token;
        Task.Factory.StartNew(() =>
        {
            while (true)
            {
                // do some heavy work here
                Thread.Sleep(100);
                if (ct.IsCancellationRequested)
                {
                    // another thread decided to cancel
                    Console.WriteLine("task canceled");
                    break;
                }
            }
        }, ct);

        // Simulate waiting 3s for the task to complete
        Thread.Sleep(3000);

        // Can't wait anymore => cancel this task 
        ts.Cancel();
        Console.ReadLine();
    }
}

5
คำอธิบายที่ดี ฉันมีคำถามว่ามันทำงานอย่างไรเมื่อเราไม่มีวิธีที่ไม่ระบุชื่อใน Task.Factory.StartNew? like Task.Factory.StartNew (() => ProcessMyMethod (), ยกเลิก
Token

61
เกิดอะไรขึ้นถ้ามีการเรียกการบล็อกซึ่งไม่ส่งคืนภายในภารกิจการเรียกใช้งาน?
mehmet6parmak

3
@ mehmet6parmak ฉันคิดว่าสิ่งเดียวที่คุณทำได้คือใช้Task.Wait(TimeSpan / int)เพื่อกำหนดเส้นตาย (ตามเวลา) จากภายนอก
ทำเครื่องหมาย

2
เกิดอะไรขึ้นถ้าฉันได้เรียนกำหนดเองของฉันในการจัดการการดำเนินการของวิธีการภายในใหม่Task? public int StartNewTask(Action method)สิ่งที่ชอบ: ข้างในStartNewTaskวิธีที่ฉันสร้างใหม่Taskโดย: Task task = new Task(() => method()); task.Start();. ดังนั้นฉันจะจัดการได้CancellationTokenอย่างไร ฉันต้องการทราบด้วยเช่นกันว่าThreadฉันต้องใช้ตรรกะเพื่อตรวจสอบว่ามีงานบางอย่างที่ยังแขวนอยู่และฆ่าพวกเขาเมื่อForm.Closingใด ด้วยความที่ผมใช้Threads Thread.Abort()
Cheshire Cat

โอ้พระเจ้าเป็นตัวอย่างที่ไม่ดีเลย! มันเป็นเงื่อนไขบูลีนที่เรียบง่ายแน่นอนว่ามันเป็นครั้งแรกที่จะลอง! แต่นั่นคือฉันมีฟังก์ชั่นในภารกิจที่แยกต่างหากซึ่งอาจต้องใช้เวลามากพอที่จะจบลงและนึกคิดว่ามันไม่ควรรู้อะไรเกี่ยวกับเกลียวหรืออะไรก็ตาม ดังนั้นฉันจะยกเลิกฟังก์ชั่นด้วยคำแนะนำของคุณได้อย่างไร
Hi-Angel

32

การยกเลิกงานนั้นเป็นไปได้อย่างง่ายดายหากคุณดักจับเธรดที่งานกำลังทำงานอยู่นี่คือตัวอย่างโค้ดที่แสดงให้เห็นถึงสิ่งนี้:

void Main()
{
    Thread thread = null;

    Task t = Task.Run(() => 
    {
        //Capture the thread
        thread = Thread.CurrentThread;

        //Simulate work (usually from 3rd party code)
        Thread.Sleep(1000);

        //If you comment out thread.Abort(), then this will be displayed
        Console.WriteLine("Task finished!");
    });

    //This is needed in the example to avoid thread being still NULL
    Thread.Sleep(10);

    //Cancel the task by aborting the thread
    thread.Abort();
}

ฉันใช้ Task.Run () เพื่อแสดงกรณีการใช้งานที่พบบ่อยที่สุด - ใช้ความสะดวกสบายของ Tasks ด้วยโค้ดเดี่ยวเธรดเก่าซึ่งไม่ได้ใช้คลาส CancellationTokenSource เพื่อพิจารณาว่าควรยกเลิกหรือไม่


2
ขอบคุณสำหรับความคิดนี้ ที่ใช้วิธีการนี้ในการดำเนินการหมดเวลาสำหรับรหัสภายนอกบางอย่างที่ไม่เคยมีใครCancellationTokenสนับสนุน ...
คริสโตตำรวจ

7
AFAIK thread.abort จะทำให้คุณไม่รู้เกี่ยวกับสแต็กของคุณซึ่งอาจไม่ถูกต้อง ฉันไม่เคยลองเลย แต่ฉันคิดว่าการเริ่มหัวข้อในโดเมนแอพแยกต่างหากที่ thread.abort จะถูกบันทึกไว้! นอกจากนี้เธรดทั้งหมดจะสูญเปล่าในโซลูชันของคุณเพื่อยกเลิกภารกิจเดียว คุณไม่จำเป็นต้องใช้งาน แต่ต้องทำเธรด (downvote)
Martin Meeser

1
ตามที่ฉันเขียน - วิธีนี้เป็นวิธีสุดท้ายที่อาจพิจารณาภายใต้สถานการณ์บางอย่าง แน่นอนCancellationTokenควรพิจารณาโซลูชันที่เรียบง่ายกว่าหรือปราศจากเงื่อนไขการแข่งขัน รหัสข้างต้นแสดงให้เห็นถึงวิธีการเท่านั้นไม่ใช่พื้นที่การใช้งาน
Florian Rappl

8
ฉันคิดว่าวิธีการนี้อาจมีผลที่ไม่รู้จักและฉันจะไม่แนะนำในรหัสการผลิต ไม่รับประกันว่าจะมีการเรียกใช้งานในเธรดที่แตกต่างกันเสมอซึ่งหมายความว่าสามารถรันได้ในเธรดเดียวกับที่สร้างขึ้นหากตัวกำหนดตารางเวลาตัดสินใจดังนั้น (ซึ่งจะหมายถึงเธรดหลักจะถูกส่งผ่านไปยังthreadตัวแปรท้องถิ่น) ในรหัสของคุณคุณอาจสิ้นสุดการยกเลิกเธรดหลักซึ่งไม่ใช่สิ่งที่คุณต้องการ บางทีการตรวจสอบว่าเธรดเหมือนกันก่อนการยกเลิกจะเป็นความคิดที่ดีหรือไม่หากคุณยืนยันในการยกเลิก
Ivaylo Slavov

10
@ มาร์ติน - เมื่อฉันได้ค้นคว้าคำถามนี้ใน SO ฉันเห็นว่าคุณได้รับคำตอบหลายข้อที่ใช้ Thread.Abort เพื่อฆ่างาน แต่คุณยังไม่ได้แก้ปัญหาทางเลือกใด ๆ คุณจะฆ่ารหัสบุคคลที่สามที่ไม่สนับสนุนการยกเลิกและทำงานในภารกิจได้อย่างไร
Gordon Bean

32

เช่นเดียวกับโพสต์นี้แสดงให้เห็นสิ่งนี้สามารถทำได้ในวิธีต่อไปนี้:

int Foo(CancellationToken token)
{
    Thread t = Thread.CurrentThread;
    using (token.Register(t.Abort))
    {
        // compute-bound work here
    }
}

แม้ว่ามันจะใช้งานได้ แต่ก็ไม่แนะนำให้ใช้วิธีการดังกล่าว หากคุณสามารถควบคุมรหัสที่ดำเนินการได้คุณควรดำเนินการยกเลิกอย่างเหมาะสม


6
+1 สำหรับการให้แนวทางที่แตกต่างขณะที่ระบุถึงทางเลือก ฉันไม่รู้ว่าสามารถทำสิ่งนี้ได้ :)
Joel

1
ขอบคุณสำหรับการแก้ปัญหา! เราสามารถส่งโทเค็นไปยังเมธอดและยกเลิกโทเค็นซอร์สแทนการรับอินสแตนซ์เธรดจากเมธอดและยกเลิกอินสแตนซ์นี้โดยตรง
แซม

เธรดของงานที่ถูกยกเลิกอาจเป็นเธรดพูลเธรดหรือเธรดของผู้เรียกที่เรียกใช้งาน เพื่อหลีกเลี่ยงนี้คุณสามารถใช้ TaskScheduler เพื่อระบุหัวข้อเฉพาะสำหรับงาน
Edward Brey

19

เรื่องแบบนี้เป็นหนึ่งในเหตุผลด้านลอจิสติกว่าทำไมAbortเลิกใช้แล้ว ก่อนอื่นอย่าใช้Thread.Abort()เพื่อยกเลิกหรือหยุดเธรดถ้าเป็นไปได้ทั้งหมด Abort()ควรใช้เพื่อฆ่าเธรดที่ไม่ตอบสนองต่อคำร้องขอสงบมากกว่าที่จะหยุดในเวลาที่เหมาะสมเท่านั้น

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


8

คุณไม่ควรพยายามทำสิ่งนี้โดยตรง ออกแบบงานของคุณให้ทำงานกับCancellationTokenและยกเลิกด้วยวิธีนี้

นอกจากนี้ฉันขอแนะนำให้เปลี่ยนเธรดหลักของคุณให้ทำงานผ่าน CancellationToken เช่นกัน การโทรThread.Abort()เป็นแนวคิดที่ไม่ดี - อาจนำไปสู่ปัญหาต่าง ๆ ที่ยากต่อการวินิจฉัย แต่เธรดนั้นสามารถใช้การยกเลิกแบบเดียวกับที่งานของคุณใช้และสามารถใช้เธรดเดียวกันCancellationTokenSourceเพื่อเริ่มการยกเลิกงานทั้งหมดและเธรดหลักของคุณ

สิ่งนี้จะนำไปสู่การออกแบบที่เรียบง่ายและปลอดภัยยิ่งขึ้น


8

ในการตอบคำถามของ Prerak K เกี่ยวกับวิธีใช้ CancellationTokens เมื่อไม่ได้ใช้วิธีที่ไม่ระบุชื่อใน Task.Factory.StartNew () คุณจะผ่าน CancellationToken เป็นพารามิเตอร์ในวิธีที่คุณเริ่มต้นด้วย StartNew () ดังที่แสดงในตัวอย่าง MSDN ที่นี่

เช่น

var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

Task.Factory.StartNew( () => DoSomeWork(1, token), token);

static void DoSomeWork(int taskNum, CancellationToken ct)
{
    // Do work here, checking and acting on ct.IsCancellationRequested where applicable, 

}

7

ฉันใช้วิธีการผสมเพื่อยกเลิกงาน

  • ประการแรกฉันพยายามที่จะยกเลิกมันสุภาพกับการใช้การยกเลิก
  • หากยังคงทำงานอยู่ (เช่นเนื่องจากความผิดพลาดของนักพัฒนาซอฟต์แวร์) จงใช้งานผิดและฆ่าโดยใช้วิธีการยกเลิกโรงเรียนเก่า

ชำระเงินตัวอย่างด้านล่าง:

private CancellationTokenSource taskToken;
private AutoResetEvent awaitReplyOnRequestEvent = new AutoResetEvent(false);

void Main()
{
    // Start a task which is doing nothing but sleeps 1s
    LaunchTaskAsync();
    Thread.Sleep(100);
    // Stop the task
    StopTask();
}

/// <summary>
///     Launch task in a new thread
/// </summary>
void LaunchTaskAsync()
{
    taskToken = new CancellationTokenSource();
    Task.Factory.StartNew(() =>
        {
            try
            {   //Capture the thread
                runningTaskThread = Thread.CurrentThread;
                // Run the task
                if (taskToken.IsCancellationRequested || !awaitReplyOnRequestEvent.WaitOne(10000))
                    return;
                Console.WriteLine("Task finished!");
            }
            catch (Exception exc)
            {
                // Handle exception
            }
        }, taskToken.Token);
}

/// <summary>
///     Stop running task
/// </summary>
void StopTask()
{
    // Attempt to cancel the task politely
    if (taskToken != null)
    {
        if (taskToken.IsCancellationRequested)
            return;
        else
            taskToken.Cancel();
    }

    // Notify a waiting thread that an event has occurred
    if (awaitReplyOnRequestEvent != null)
        awaitReplyOnRequestEvent.Set();

    // If 1 sec later the task is still running, kill it cruelly
    if (runningTaskThread != null)
    {
        try
        {
            runningTaskThread.Join(TimeSpan.FromSeconds(1));
        }
        catch (Exception ex)
        {
            runningTaskThread.Abort();
        }
    }
}

4

งานมีการสนับสนุนชั้นแรกสำหรับการยกเลิกผ่านราชสกุลยกเลิก สร้างงานของคุณด้วยโทเค็นการยกเลิกและยกเลิกงานผ่านสิ่งเหล่านี้อย่างชัดเจน


4

คุณสามารถใช้CancellationTokenเพื่อควบคุมว่างานจะถูกยกเลิกหรือไม่ คุณกำลังพูดถึงการยกเลิกก่อนที่จะเริ่มต้น ("ไม่เป็นไรฉันทำไปแล้ว") หรือขัดขวางการใช้งานจริงกลาง? หากอดีตCancellationTokenสามารถเป็นประโยชน์ได้ หากหลังคุณอาจต้องใช้กลไก "การประกันตัว" ของคุณเองและตรวจสอบจุดที่เหมาะสมในการปฏิบัติงานว่าคุณควรจะล้มเหลวอย่างรวดเร็ว (คุณยังสามารถใช้ CancellationToken เพื่อช่วยคุณได้ แต่เป็นคู่มือเล็กน้อย)

MSDN มีบทความเกี่ยวกับการยกเลิกงาน: http://msdn.microsoft.com/en-us/library/dd997396.aspx


3

งานกำลังถูกดำเนินการบน ThreadPool (อย่างน้อยถ้าคุณใช้ค่าเริ่มต้นจากโรงงาน) ดังนั้นการยกเลิกเธรดจะไม่ส่งผลกระทบต่องาน สำหรับการยกเลิกงานให้ดูที่การยกเลิกงานบน msdn


1

ฉันพยายามCancellationTokenSourceแต่ฉันทำไม่ได้ และฉันก็ทำสิ่งนี้ด้วยวิธีของฉันเอง และมันใช้งานได้

namespace Blokick.Provider
{
    public class SignalRConnectProvider
    {
        public SignalRConnectProvider()
        {
        }

        public bool IsStopRequested { get; set; } = false; //1-)This is important and default `false`.

        public async Task<string> ConnectTab()
        {
            string messageText = "";
            for (int count = 1; count < 20; count++)
            {
                if (count == 1)
                {
                //Do stuff.
                }

                try
                {
                //Do stuff.
                }
                catch (Exception ex)
                {
                //Do stuff.
                }
                if (IsStopRequested) //3-)This is important. The control of the task stopping request. Must be true and in inside.
                {
                    return messageText = "Task stopped."; //4-) And so return and exit the code and task.
                }
                if (Connected)
                {
                //Do stuff.
                }
                if (count == 19)
                {
                //Do stuff.
                }
            }
            return messageText;
        }
    }
}

และคลาสอื่นของการเรียกเมธอด:

namespace Blokick.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MessagePerson : ContentPage
    {
        SignalRConnectProvider signalR = new SignalRConnectProvider();

        public MessagePerson()
        {
            InitializeComponent();

            signalR.IsStopRequested = true; // 2-) And this. Make true if running the task and go inside if statement of the IsStopRequested property.

            if (signalR.ChatHubProxy != null)
            {
                 signalR.Disconnect();
            }

            LoadSignalRMessage();
        }
    }
}

0

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

TaskSchedulerเพื่อให้แน่ใจว่างานที่ได้รับหัวข้อของตัวเองสร้างตารางเวลาที่กำหนดเองที่ได้มาจาก ในการนำไปใช้ของคุณQueueTaskให้สร้างเธรดใหม่และใช้เพื่อเรียกใช้งาน ThreadAbortExceptionหลังจากนั้นคุณสามารถยกเลิกด้ายซึ่งจะทำให้งานที่จะเสร็จสมบูรณ์ในรัฐโทษฐานที่มี

ใช้ตัวกำหนดตารางงานนี้:

class SingleThreadTaskScheduler : TaskScheduler
{
    public Thread TaskThread { get; private set; }

    protected override void QueueTask(Task task)
    {
        TaskThread = new Thread(() => TryExecuteTask(task));
        TaskThread.Start();
    }

    protected override IEnumerable<Task> GetScheduledTasks() => throw new NotSupportedException(); // Unused
    protected override bool NotSupportedException(Task task, bool taskWasPreviouslyQueued) => throw new NotSupportedException(); // Unused
}

เริ่มงานของคุณเช่นนี้:

var scheduler = new SingleThreadTaskScheduler();
var task = Task.Factory.StartNew(action, cancellationToken, TaskCreationOptions.LongRunning, scheduler);

หลังจากนั้นคุณสามารถยกเลิกด้วย:

scheduler.TaskThread.Abort();

โปรดทราบว่าข้อควรทราบเกี่ยวกับการยกเลิกเธรดยังคงมีผล:

Thread.Abortวิธีควรใช้ด้วยความระมัดระวัง โดยเฉพาะอย่างยิ่งเมื่อคุณเรียกใช้เพื่อยกเลิกเธรดอื่นนอกเหนือจากเธรดปัจจุบันคุณไม่ทราบว่าโค้ดใดที่ได้ดำเนินการหรือล้มเหลวในการดำเนินการเมื่อThreadAbortExceptionถูกส่งออกไปและคุณไม่สามารถมั่นใจได้ว่าสถานะของแอปพลิเคชันของคุณ รับผิดชอบในการถนอมรักษา ตัวอย่างเช่นการโทรThread.Abortอาจป้องกันการสร้างแบบคงที่จากการดำเนินการหรือป้องกันการปล่อยของทรัพยากรที่ไม่มีการจัดการ


รหัสนี้ล้มเหลวด้วยข้อยกเว้นรันไทม์: System.InvalidOperationException: RunSynchronously อาจไม่สามารถเรียกบนงานที่เริ่มแล้ว
Theodor Zoulias

1
@TheodorZoulias จับดี ขอบคุณ ฉันแก้ไขรหัสและปรับปรุงคำตอบโดยทั่วไป
Edward Brey

1
อ๊ะสิ่งนี้ได้แก้ไขข้อผิดพลาด ข้อแม้อื่นที่ควรจะกล่าวถึงก็Thread.Abortคือไม่ได้รับการสนับสนุนบน. NET Core ความพยายามที่จะใช้มีผลให้เกิดข้อยกเว้น: System.PlatformNotSupportedException: การยกเลิกแทร็กไม่ได้รับการสนับสนุนบนแพลตฟอร์มนี้ ข้อแม้ที่สามคือSingleThreadTaskSchedulerไม่สามารถใช้งานได้อย่างมีประสิทธิภาพกับงานตามสไตล์สัญญาหรือกล่าวอีกนัยหนึ่งกับงานที่สร้างขึ้นโดยasyncผู้รับมอบสิทธิ์ ตัวอย่างเช่นการฝังตัวที่await Task.Delay(1000)ทำงานในไม่มีเธรดดังนั้นจึงไม่ได้รับผลกระทบเป็นเหตุการณ์ของเธรด
Theodor Zoulias
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.