簡體   English   中英

如何中止/取消 TPL 任務?

[英]How do I abort/cancel TPL Tasks?

在一個線程中,我創建了一些System.Threading.Task並啟動了每個任務。

當我執行.Abort()來終止線程時,任務不會中止。

如何將.Abort()傳輸到我的任務?

你不能。 任務使用線程池中的后台線程。 也不推薦使用 Abort 方法取消線程。 您可以查看以下博客文章,其中解釋了使用取消令牌取消任務的正確方法。 下面是一個例子:

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

如果您捕獲運行任務的線程,則很容易中止任務。 下面是一個示例代碼來演示這一點:

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 類來確定是否應該取消它。

就像這篇文章建議的那樣,這可以通過以下方式完成:

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

雖然它有效,但不建議使用這種方法。 如果您可以控制在任務中執行的代碼,則最好對取消進行適當的處​​理。

這種事情是Abort被棄用的邏輯原因之一。 首先,如果可能不要使用Thread.Abort()取消或停止線程。 Abort()應該只用於強行殺死一個沒有響應更和平的請求的線程,以便及時停止。

話雖如此,您需要提供一個共享取消指示器,一個線程設置並等待,而另一個線程定期檢查並正常退出。 .NET 4 包含一個專門為此目的設計的結構,即CancellationToken

要回答 Prera​​k K 關於在 Task.Factory.StartNew() 中不使用匿名方法時如何使用 CancellationTokens 的問題,您將 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, 

}

您不應該嘗試直接執行此操作。 設計您的任務以使用CancellationToken ,並以這種方式取消它們。

此外,我建議您也將主線程更改為通過 CancellationToken 運行。 調用Thread.Abort()是一個壞主意 - 它會導致各種很難診斷的問題。 相反,該線程可以使用相同的取消,你的任務使用-和同CancellationTokenSource可以用來觸發所有任務的取消和你的主線程。

這將導致更簡單、更安全的設計。

我使用混合方法取消任務。

  • 首先,我嘗試使用Cancellation禮貌地取消它。
  • 如果它仍在運行(例如,由於開發人員的錯誤),則行為不端並使用老派的Abort方法將其殺死。

查看下面的示例:

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

任務通過取消令牌具有一流的取消支持。 使用取消令牌創建您的任務,並通過這些明確取消任務。

您可以使用CancellationToken來控制任務是否被取消。 您是在談論在它開始之前中止它(“沒關系,我已經這樣做了”),還是實際上在中間中斷了它? 如果是前者, CancellationToken會有所幫助; 如果是后者,您可能需要實現自己的“救助”機制,並在任務執行的適當點檢查是否應該快速失敗(您仍然可以使用 CancellationToken 來幫助您,但需要更多手動操作)。

MSDN 有一篇關於取消任務的文章: http : //msdn.microsoft.com/en-us/library/dd997396.aspx

任務正在 ThreadPool 上執行(至少,如果您使用的是默認工廠),因此中止線程不會影響任務。 有關中止任務,請參閱 msdn 上的任務取消

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

如果可以使任務在其自己的線程上創建並在其Thread對象上調用Abort ,則可以像線程一樣中止任務。 默認情況下,任務在線程池線程或調用線程上運行 - 您通常不想中止這兩個線程。

為確保任務獲得自己的線程,請創建一個派生自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 TryExecuteTaskInline(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可能會阻止靜態構造函數執行或阻止釋放非托管資源。

您可以使用此 class..:它適用於所有類型的返回值..

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace CarNUChargeTester
{
    public class TimeOutTaskRunner<T>
    {
        private Func<T> func;
        private int sec;
        private T result;
        public TimeOutTaskRunner(Func<T> func, int sec)
        {
            this.func = func;
            this.sec = sec;
        }

        public bool run()
        {
            var scheduler = new SingleThreadTaskScheduler();
            Task<T> task = Task<T>.Factory.StartNew(func, (new CancellationTokenSource()).Token, TaskCreationOptions.LongRunning, scheduler);
            if (!task.Wait(TimeSpan.FromSeconds(sec)))
            {
                scheduler.TaskThread.Abort();
                return false;
            }
            result = task.Result;
            return true;
        }
        public T getResult() { return result; }
    }
    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();
        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) => throw new NotSupportedException();
    }
}

要使用它,你可以這樣寫:

TimeOutTaskRunner<string> tr = new TimeOutTaskRunner<string>(f, 10); // 10 sec to run f
                if (!tr.run())
                    errorMsg("TimeOut"); !! My func
                tr.getResult() // get the results if it done without timeout..

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM