如何中止/取消TPL任务?

时间:2011-01-24 15:42:17

标签: c# .net multithreading task abort

在一个帖子中,我创建了一些System.Threading.Task并开始执行每项任务。

当我执行.Abort()来终止线程时,任务不会中止。

如何将.Abort()传送到我的任务?

12 个答案:

答案 0 :(得分:207)

你做不到。任务使用线程池中的后台线程。也不建议使用Abort方法取消线程。您可以查看following blog post,它解释了使用取消令牌取消任务的正确方法。这是一个例子:

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

答案 1 :(得分:27)

如果捕获正在运行任务的线程,则可以轻松地中止任务。以下是一个示例代码来演示:

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 :(得分:26)

this post所示,这可以通过以下方式完成:

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

虽然它有效,但不建议使用这种方法。如果你可以控制在任务中执行的代码,你最好还是正确处理取消。

答案 3 :(得分:17)

这种事情是弃用Abort的后勤原因之一。首先,如果可能的话,不要使用Thread.Abort()来取消或停止线程。 Abort()只应用于强制终止没有响应更多线程的线程和平要求及时制止。

话虽这么说,你需要提供一个共享取消指示器,一个线程设置并等待,而另一个线程定期检查并正常退出。 .NET 4包含专门为此目的而设计的结构CancellationToken

答案 4 :(得分:7)

您不应该尝试直接这样做。设计您的任务以使用CancellationToken,并以这种方式取消它们。

此外,我还建议您通过CancellationToken更改主线程的功能。调用Thread.Abort()是一个坏主意 - 它可能导致各种难以诊断的问题。相反,该线程可以使用您的任务使用的相同Cancellation - 并且可以使用相同的CancellationTokenSource来触发取消所有任务和主线程。

这将导致更简单,更安全的设计。

答案 5 :(得分:7)

要回答Prera​​k K关于如何在Task.Factory.StartNew()中不使用匿名方法时使用CancellationTokens的问题,请将CancellationToken作为参数传递给您从StartNew()开始的方法,如下所示: MSDN示例here

e.g。

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, 

}

答案 6 :(得分:6)

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

  • 首先,我正在尝试使用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();
        }
    }
}

答案 7 :(得分:3)

您可以使用CancellationToken来控制任务是否被取消。你是在谈论在它开始之前中止它(“没关系,我已经做过这个”),或者实际上在中间打断它?如果是前者,CancellationToken可以提供帮助;如果是后者,你可能需要实现自己的“纾困”机制,并在任务执行的适当位置检查你是否应该快速失败(你仍然可以使用CancellationToken来帮助你,但它更多的是手动)。

MSDN有一篇关于取消任务的文章: http://msdn.microsoft.com/en-us/library/dd997396.aspx

答案 8 :(得分:2)

正在ThreadPool上执行任务(至少,如果您使用的是默认工厂),因此中止线程不会影响任务。有关中止任务的信息,请参阅msdn。上的Task Cancellation

答案 9 :(得分:2)

任务通过cancellation tokens获得取消的一流支持。使用取消令牌创建任务,并通过这些明确取消任务。

答案 10 :(得分:0)

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

答案 11 :(得分: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();

请注意,caveat about aborting a thread仍然适用:

请谨慎使用Thread.Abort方法。尤其是当您调用它以中止当前线程以外的其他线程时,您不知道在抛出ThreadAbortException时执行了什么代码或执行了什么代码,也无法确定应用程序或任何其他应用程序的状态应用程序和用户声明它负责保存。例如,调用Thread.Abort可能会阻止静态构造函数执行或阻止释放非托管资源。