如何中止/取消TPL任务?


Answers:


228

你不能 任务使用线程池中的后台线程。另外,也不建议使用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();
    }
}

5
很好的解释。我有一个问题,当Task.Factory.StartNew中没有匿名方法时,它如何工作?像Task.Factory.StartNew(()=> ProcessMyMethod(),cancellationToken)
Prera​​k K

61
如果执行中的任务没有返回阻塞调用怎么办?
mehmet6parmak

3
@ mehmet6parmak我认为您唯一可以做的就是Task.Wait(TimeSpan / int)从外部给它一个(基于时间的)截止日期。
2014年

2
如果我有自定义类来管理新类中方法的执行该Task怎么办?类似于:public int StartNewTask(Action method)。在StartNewTask方法内部,我创建了一个新方法TaskTask task = new Task(() => method()); task.Start();。那么我该如何管理CancellationToken?我还想知道是否Thread需要实现一种逻辑来检查是否仍有某些任务挂起,因此在执行时将其杀死Form.Closing。与Threads我一起使用Thread.Abort()
柴郡猫

天哪,这是一个不好的例子!这是一个简单的布尔条件,当然这是第一个尝试的条件!但是,也就是说,我在单独的Task中有一个函数,可能要花很多时间才能结束,理想情况下,它应该不了解任何有关线程的信息。那么,如何根据您的建议取消该功能?
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支持...
Christoph Fink 2014年

7
AFAIK thread.abort将使您不知道堆栈信息,它可能无效。我从未尝试过,但是我猜想在单独的应用程序域中启动线程将可以保存thread.abort!此外,您的解决方案浪费了整个线程,只是为了中止一项任务。您不必使用任务,而是首先使用线程。(downvote)
Martin Meeser 2014年

1
如我所写-此解决方案是在某些情况下可以考虑的不得已的方法。当然CancellationToken,应该考虑一个甚至没有竞争条件的简单解决方案。上面的代码仅说明了方法,未说明使用范围。
Florian Rappl 2014年

8
我认为这种方法可能会产生未知的后果,因此我不会在生产代码中推荐这种方法。任务不一定总是在不同的线程中执行,这意味着如果调度程序决定将它们与创建它们的线程运行在同一线程中(这意味着主线程将被传递到thread局部变量)。在代码中,您可能最终会中止主线程,这不是您真正想要的。如果您坚持要中止,也许最好在中止之前检查线程是否相同
Ivaylo Slavov 2015年

10
@Martin-正如我在SO上研究此问题一样,我看到您否决了使用Thread.Abort杀死任务的几个答案,但是您没有提供任何替代解决方案。如何杀死不支持取消并且正在Task中运行的第三方代码
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为task指定一个专用线程
爱德华·布雷

19

这种事情是为什么Abort过时的后勤原因之一。首先,尽可能不要使用Thread.Abort()来取消或停止线程。 Abort()仅应用于强制杀死对及时停止的更和平请求没有响应的线程。

就是说,您需要提供一个共享的取消指示器,一个线程设置并等待另一个线程定期检查并正常退出,然后等待。.NET 4包括专门为此目的设计的结构CancellationToken


8

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

另外,我建议您也更改您的主线程以通过CancellationToken起作用。打电话Thread.Abort()是一个坏主意-它可能导致各种问题,这些问题很难诊断。相反,该线程可以使用您的任务使用的相同的Cancellation(取消),并且该线程可以CancellationTokenSource用于触发所有任务和主线程的取消。

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


8

要回答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, 

}

7

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

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


4

您可以使用CancellationToken来控制是否取消任务。您是在谈论在启动它之前终止它(“没关系,我已经这样做了”),还是实际上在中间中断了它?如果是前者,CancellationToken可能会有所帮助;如果是后者,则可能需要实现自己的“纾困”机制,并在任务执行的适当位置检查是否应该快速失败(您仍然可以使用CancellationToken来帮助您,但是要多手动一些)。

MSDN上有一篇有关取消任务的文章:http : //msdn.microsoft.com/zh-cn/library/dd997396.aspx



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好收获。谢谢。我修复了代码,并总体上改善了答案。
爱德华·布雷

1
是的,这已修复了该错误。Thread.Abort.NET Core不支持应注意的另一个警告。尝试使用它会导致异常:System.PlatformNotSupportedException:在此平台上不支持线程中止。第三个警告是,SingleThreadTaskScheduler不能将其有效地用于诺言式任务,换句话说,不能用于由async委托创建的任务。例如,嵌入式程序await Task.Delay(1000)在无线程中运行,因此它不受线程事件的影响。
西奥多·祖利亚斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.