Làm cách nào để hủy bỏ / hủy bỏ Nhiệm vụ TPL?


156

Trong một chủ đề, tôi tạo một số System.Threading.Taskvà bắt đầu mỗi nhiệm vụ.

Khi tôi thực hiện .Abort()để giết luồng, các tác vụ không bị hủy bỏ.

Làm thế nào tôi có thể truyền tải các .Abort()nhiệm vụ của tôi?


Câu trả lời:


228

Bạn không thể. Nhiệm vụ sử dụng chủ đề nền từ nhóm chủ đề. Ngoài ra, hủy bỏ các chủ đề bằng phương pháp Abort không được khuyến khích. Bạn có thể xem bài đăng trên blog sau đây giải thích cách hủy tác vụ phù hợp bằng cách sử dụng mã thông báo hủy. Đây là một ví dụ:

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
Giải thích tốt đẹp. Tôi có một câu hỏi, làm thế nào nó hoạt động khi chúng ta không có một phương thức ẩn danh trong Task.Factory.StartNew? như Task.Factory.StartNew (() => ProcessMyMethod (),
hủyToken

61
Điều gì xảy ra nếu có một cuộc gọi chặn không trở lại bên trong tác vụ thực thi?
mehmet6parmak

3
@ mehmet6parmak Tôi nghĩ rằng điều duy nhất bạn có thể làm sau đó là sử dụng Task.Wait(TimeSpan / int)để đưa ra thời hạn (dựa trên thời gian) từ bên ngoài.
Đánh dấu

2
Điều gì xảy ra nếu tôi có lớp tùy chỉnh của mình để quản lý việc thực thi các phương thức bên trong một phương thức mới Task? Một cái gì đó như : public int StartNewTask(Action method). Bên trong StartNewTaskphương thức tôi tạo mới Taskbằng : Task task = new Task(() => method()); task.Start();. Vì vậy, làm thế nào tôi có thể quản lý CancellationToken? Tôi cũng muốn biết nếu Threadtôi cần phải thực hiện logic để kiểm tra xem có một số Nhiệm vụ vẫn đang treo hay không và vì vậy hãy giết chúng khi nào Form.Closing. Với Threadstôi sử dụng Thread.Abort().
Cheshire Cat

Ôi, thật là một ví dụ tồi tệ! Đó là một điều kiện boolean đơn giản, tất nhiên đó là điều đầu tiên người ta sẽ thử! Nhưng tức là tôi có một chức năng trong một Nhiệm vụ riêng biệt, có thể mất nhiều thời gian để kết thúc và lý tưởng là nó không nên biết bất cứ điều gì về một luồng hoặc bất cứ điều gì. Vậy làm thế nào để tôi hủy chức năng với lời khuyên của bạn?
Hi-Angel

32

Việc hủy bỏ một Nhiệm vụ có thể dễ dàng nếu bạn nắm bắt được luồng mà tác vụ đang chạy. Dưới đây là một mã ví dụ để chứng minh điều này:

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

Tôi đã sử dụng Task.Run () để hiển thị trường hợp sử dụng phổ biến nhất cho trường hợp này - sử dụng sự thoải mái của Nhiệm vụ với mã đơn luồng cũ, không sử dụng lớp CancellingTokenSource để xác định xem có nên hủy bỏ hay không.


2
Cảm ơn ý tưởng này. Đã sử dụng phương pháp này để thực hiện thời gian chờ cho một số mã bên ngoài, không CancellationTokenhỗ trợ ...
Christoph Fink

7
AFAIK thread.abort sẽ khiến bạn không biết về ngăn xếp của mình, nó có thể không hợp lệ. Tôi chưa bao giờ thử nó nhưng tôi đoán bắt đầu một chủ đề trong miền ứng dụng riêng biệt mà thread.abort sẽ được lưu! Bên cạnh đó, toàn bộ một chủ đề bị lãng phí trong giải pháp của bạn chỉ để hủy bỏ một nhiệm vụ. Bạn sẽ không phải sử dụng các tác vụ nhưng chủ đề ở vị trí đầu tiên. (downvote)
Martin Meeser

1
Như tôi đã viết - giải pháp này là giải pháp cuối cùng có thể được xem xét trong một số trường hợp nhất định. Tất nhiên, một CancellationTokenhoặc thậm chí các giải pháp đơn giản hơn là không có điều kiện chủng tộc nên được xem xét. Mã ở trên chỉ minh họa phương thức, không phải khu vực sử dụng.
Florian Rappl

8
Tôi nghĩ rằng cách tiếp cận này có thể có hậu quả chưa biết và tôi sẽ không đề xuất nó trong mã sản xuất. Các tác vụ không phải lúc nào cũng được đảm bảo để được thực thi trong một luồng khác, có nghĩa là chúng có thể được chạy trong cùng một luồng với luồng đã tạo nếu trình lập lịch quyết định như vậy (điều đó có nghĩa là luồng chính sẽ được chuyển đến threadbiến cục bộ). Trong mã của bạn, sau đó bạn có thể hủy bỏ chủ đề chính, đó không phải là điều bạn thực sự muốn. Có lẽ một kiểm tra nếu các chủ đề là giống nhau trước khi phá thai sẽ là một suy nghĩ tốt để có, nếu bạn khăng khăng phá thai
Ivaylo Slavov

10
@Martin - Như tôi đã nghiên cứu câu hỏi này trên SO, tôi thấy bạn đã bỏ phiếu xuống một số câu trả lời sử dụng Thread.Abort để tiêu diệt các tác vụ, nhưng bạn chưa cung cấp bất kỳ giải pháp thay thế nào. Làm thế nào bạn có thể giết mã bên thứ 3 không hỗ trợ hủy và đang chạy trong Tác vụ ?
Gordon Bean

32

Giống như bài viết này gợi ý, điều này có thể được thực hiện theo cách sau:

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

Mặc dù nó hoạt động, nhưng không nên sử dụng cách tiếp cận như vậy. Nếu bạn có thể kiểm soát mã thực thi trong tác vụ, tốt hơn hết là bạn nên xử lý hủy hợp lý.


6
+1 để đưa ra một cách tiếp cận khác trong khi nêu rõ những nhược điểm của nó. Tôi không biết điều này có thể được thực hiện :)
Joel

1
Cảm ơn giải pháp! Chúng ta chỉ có thể truyền mã thông báo cho phương thức và hủy mã thông báo, thay vì bằng cách nào đó lấy phiên bản luồng từ phương thức và hủy bỏ trực tiếp trường hợp này.
Sam

Chuỗi của tác vụ bị hủy bỏ có thể là một luồng xử lý nhóm hoặc luồng của người gọi gọi tác vụ. Để tránh điều này, bạn có thể sử dụng một TaskScheduler để chỉ định một luồng chuyên dụng cho tác vụ .
Edward Brey

19

Loại điều này là một trong những lý do hậu cần tại sao Abortbị phản đối. Đầu tiên và quan trọng nhất, không sử dụng Thread.Abort()để hủy bỏ hoặc dừng một chủ đề nếu có thể. Abort()chỉ nên được sử dụng để giết chết một chủ đề không đáp ứng với các yêu cầu hòa bình hơn để dừng lại một cách kịp thời.

Điều đó đang được nói, bạn cần cung cấp một chỉ báo hủy được chia sẻ rằng một luồng sẽ đặt và chờ trong khi luồng khác kiểm tra định kỳ và thoát ra một cách duyên dáng. .NET 4 bao gồm một cấu trúc được thiết kế dành riêng cho mục đích này , CancellationToken.


8

Bạn không nên cố gắng làm điều này trực tiếp. Thiết kế các nhiệm vụ của bạn để làm việc với CancellingToken và hủy chúng theo cách này.

Ngoài ra, tôi cũng khuyên bạn nên thay đổi chủ đề chính của mình để hoạt động thông qua CancellingToken. Gọi điện Thread.Abort()là một ý tưởng tồi - nó có thể dẫn đến nhiều vấn đề rất khó chẩn đoán. Thay vào đó, luồng đó có thể sử dụng cùng một Hủy mà các tác vụ của bạn sử dụng - và CancellationTokenSourcecó thể được sử dụng tương tự để kích hoạt hủy tất cả các tác vụ và luồng chính của bạn.

Điều này sẽ dẫn đến một thiết kế đơn giản hơn và an toàn hơn.


8

Để trả lời câu hỏi của Prerak K về cách sử dụng CancellingTokens khi không sử dụng phương thức ẩn danh trong Task.Factory.StartNew (), bạn chuyển CancellingToken làm tham số cho phương thức bạn bắt đầu với StartNew (), như trong ví dụ MSDN ở đây .

ví dụ

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

Tôi sử dụng một cách tiếp cận hỗn hợp để hủy bỏ một nhiệm vụ.

  • Đầu tiên, tôi đang cố gắng hủy bỏ nó một cách lịch sự bằng cách sử dụng Hủy bỏ .
  • Nếu nó vẫn chạy (ví dụ do lỗi của nhà phát triển), thì hãy xử lý sai và giết nó bằng phương pháp Abort trường học cũ .

Kiểm tra một ví dụ dưới đây:

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

Nhiệm vụ có hỗ trợ hạng nhất để hủy qua mã thông báo hủy . Tạo các nhiệm vụ của bạn bằng các mã thông báo hủy và hủy các tác vụ thông qua các tác vụ này một cách rõ ràng.


4

Bạn có thể sử dụng a CancellationTokenđể kiểm soát xem tác vụ có bị hủy hay không. Bạn đang nói về việc hủy bỏ nó trước khi nó bắt đầu ("đừng bận tâm, tôi đã làm điều này"), hoặc thực sự làm gián đoạn nó ở giữa? Nếu trước đây, CancellationTokencó thể hữu ích; nếu sau này, có lẽ bạn sẽ cần phải thực hiện cơ chế "bảo lãnh" của riêng mình và kiểm tra các điểm thích hợp trong quá trình thực thi nhiệm vụ xem bạn có nên thất bại nhanh không (bạn vẫn có thể sử dụng CancellingToken để giúp bạn, nhưng thủ công hơn một chút).

MSDN có một bài viết về việc hủy bỏ Nhiệm vụ: http://msdn.microsoft.com/en-us/l Library / dd997394.aspx


3

Tác vụ đang được thực thi trên ThreadPool (ít nhất, nếu bạn đang sử dụng nhà máy mặc định), vì vậy việc hủy bỏ luồng có thể ảnh hưởng đến các tác vụ. Để hủy bỏ các nhiệm vụ, xem Hủy bỏ nhiệm vụ trên msd.


1

Tôi đã thử CancellationTokenSourcenhưng tôi không thể làm điều này. Và tôi đã làm điều này với cách riêng của tôi. Và nó hoạt động.

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;
        }
    }
}

Và một lớp khác của phương thức gọi:

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

Bạn có thể hủy bỏ một tác vụ như một luồng nếu bạn có thể khiến tác vụ được tạo trên luồng của chính nó và gọi đối tượng Abortcủa nó Thread. Theo mặc định, một tác vụ chạy trên một chuỗi nhóm luồng hoặc luồng gọi - không phải trong số đó bạn thường muốn hủy bỏ.

Để đảm bảo tác vụ có chủ đề riêng, hãy tạo một lịch trình tùy chỉnh bắt nguồn từ TaskScheduler. Trong quá trình thực hiện của bạn QueueTask, tạo một luồng mới và sử dụng nó để thực thi tác vụ. Sau đó, bạn có thể hủy bỏ luồng, điều này sẽ khiến tác vụ hoàn thành trong trạng thái bị lỗi với a ThreadAbortException.

Sử dụng lịch trình tác vụ này:

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
}

Bắt đầu nhiệm vụ của bạn như thế này:

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

Sau đó, bạn có thể hủy bỏ với:

scheduler.TaskThread.Abort();

Lưu ý rằng cảnh báo về việc hủy bỏ một chủ đề vẫn được áp dụng:

Các Thread.Abortphương pháp nên được sử dụng một cách thận trọng. Đặc biệt, khi bạn gọi nó để hủy bỏ một luồng khác với luồng hiện tại, bạn không biết mã nào đã thực thi hoặc không thực thi được khi ThreadAdortException bị ném, bạn cũng không thể chắc chắn về trạng thái của ứng dụng hoặc bất kỳ trạng thái ứng dụng và ứng dụng nào của bạn rằng nó có trách nhiệm bảo quản. Ví dụ, việc gọi Thread.Abortcó thể ngăn các nhà xây dựng tĩnh thực thi hoặc ngăn chặn việc giải phóng các tài nguyên không được quản lý.


Mã này không thành công với ngoại lệ thời gian chạy: System.InvalidOperationException: RunSynyncly có thể không được gọi trên một tác vụ đã được bắt đầu.
Theodor Zoulias

1
@TheodorZoulias Bắt tốt. Cảm ơn. Tôi đã sửa mã và thường cải thiện câu trả lời.
Edward Brey

1
Yeap, điều này đã sửa lỗi. Một cảnh báo khác mà có lẽ nên được đề cập Thread.Abortlà không được hỗ trợ trên .NET Core. Cố gắng sử dụng nó sẽ dẫn đến một ngoại lệ: System.Pl platformNotSupportedException: Hủy bỏ luồng không được hỗ trợ trên nền tảng này. Một cảnh báo thứ ba là SingleThreadTaskSchedulerkhông thể sử dụng hiệu quả với các nhiệm vụ theo kiểu hứa hẹn, nói cách khác với các nhiệm vụ được tạo với asynccác đại biểu. Ví dụ, một nhúng await Task.Delay(1000)chạy trong không có luồng, vì vậy nó không bị ảnh hưởng là các sự kiện luồng.
Theodor Zoulias
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.