简体   繁体   English

如何中止/取消 TPL 任务?

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

In a thread, I create some System.Threading.Task and start each task.在一个线程中,我创建了一些System.Threading.Task并启动了每个任务。

When I do a .Abort() to kill the thread, the tasks are not aborted.当我执行.Abort()来终止线程时,任务不会中止。

How can I transmit the .Abort() to my tasks?如何将.Abort()传输到我的任务?

You can't.你不能。 Tasks use background threads from the thread pool.任务使用线程池中的后台线程。 Also canceling threads using the Abort method is not recommended.也不推荐使用 Abort 方法取消线程。 You may take a look at the following blog post which explains a proper way of canceling tasks using cancellation tokens.您可以查看以下博客文章,其中解释了使用取消令牌取消任务的正确方法。 Here's an example:下面是一个例子:

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

Aborting a Task is easily possible if you capture the thread in which the task is running in. Here is an example code to demonstrate this:如果您捕获运行任务的线程,则很容易中止任务。 下面是一个示例代码来演示这一点:

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

I used Task.Run() to show the most common use-case for this - using the comfort of Tasks with old single-threaded code, which does not use the CancellationTokenSource class to determine if it should be canceled or not.我使用 Task.Run() 来展示最常见的用例 - 使用带有旧单线程代码的 Tasks 的舒适性,它不使用 CancellationTokenSource 类来确定是否应该取消它。

Like this post suggests, this can be done in the following way:就像这篇文章建议的那样,这可以通过以下方式完成:

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

Although it works, it's not recommended to use such approach.虽然它有效,但不建议使用这种方法。 If you can control the code that executes in task, you'd better go with proper handling of cancellation.如果您可以控制在任务中执行的代码,则最好对取消进行适当的处​​理。

This sort of thing is one of the logistical reasons why Abort is deprecated.这种事情是Abort被弃用的逻辑原因之一。 First and foremost, do not use Thread.Abort() to cancel or stop a thread if at all possible.首先,如果可能不要使用Thread.Abort()取消或停止线程。 Abort() should only be used to forcefully kill a thread that is not responding to more peaceful requests to stop in a timely fashion. Abort()应该只用于强行杀死一个没有响应更和平的请求的线程,以便及时停止。

That being said, you need to provide a shared cancellation indicator that one thread sets and waits while the other thread periodically checks and gracefully exits.话虽如此,您需要提供一个共享取消指示器,一个线程设置并等待,而另一个线程定期检查并正常退出。 .NET 4 includes a structure designed specifically for this purpose, the CancellationToken . .NET 4 包含一个专门为此目的设计的结构,即CancellationToken

To answer Prerak K's question about how to use CancellationTokens when not using an anonymous method in Task.Factory.StartNew(), you pass the CancellationToken as a parameter into the method you're starting with StartNew(), as shown in the MSDN example here .要回答 Prera​​k K 关于在 Task.Factory.StartNew() 中不使用匿名方法时如何使用 CancellationTokens 的问题,您将 CancellationToken 作为参数传递到您使用 StartNew() 开始的方法中,如 MSDN 示例所示在这里

eg例如

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, 

}

You should not try to do this directly.您不应该尝试直接执行此操作。 Design your tasks to work with a CancellationToken , and cancel them this way.设计您的任务以使用CancellationToken ,并以这种方式取消它们。

In addition, I would recommend changing your main thread to function via a CancellationToken as well.此外,我建议您也将主线程更改为通过 CancellationToken 运行。 Calling Thread.Abort() is a bad idea - it can lead to various problems that are very difficult to diagnose.调用Thread.Abort()是一个坏主意 - 它会导致各种很难诊断的问题。 Instead, that thread can use the same Cancellation that your tasks use - and the same CancellationTokenSource can be used to trigger the cancellation of all of your tasks and your main thread.相反,该线程可以使用相同的取消,你的任务使用-和同CancellationTokenSource可以用来触发所有任务的取消和你的主线程。

This will lead to a far simpler, and safer, design.这将导致更简单、更安全的设计。

I use a mixed approach to cancel a task.我使用混合方法取消任务。

  • Firstly, I'm trying to Cancel it politely with using the Cancellation .首先,我尝试使用Cancellation礼貌地取消它。
  • If it's still running (eg due to a developer's mistake), then misbehave and kill it using an old-school Abort method.如果它仍在运行(例如,由于开发人员的错误),则行为不端并使用老派的Abort方法将其杀死。

Checkout an example below:查看下面的示例:

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

Tasks have first class support for cancellation via cancellation tokens .任务通过取消令牌具有一流的取消支持。 Create your tasks with cancellation tokens, and cancel the tasks via these explicitly.使用取消令牌创建您的任务,并通过这些明确取消任务。

You can use a CancellationToken to control whether the task gets cancelled.您可以使用CancellationToken来控制任务是否被取消。 Are you talking about aborting it before it's started ("nevermind, I already did this"), or actually interrupting it in middle?您是在谈论在它开始之前中止它(“没关系,我已经这样做了”),还是实际上在中间中断了它? If the former, the CancellationToken can be helpful;如果是前者, CancellationToken会有所帮助; if the latter, you will probably need to implement your own "bail out" mechanism and check at appropriate points in the task execution whether you should fail fast (you can still use the CancellationToken to help you, but it's a little more manual).如果是后者,您可能需要实现自己的“救助”机制,并在任务执行的适当点检查是否应该快速失败(您仍然可以使用 CancellationToken 来帮助您,但需要更多手动操作)。

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

Task are being executed on the ThreadPool (at least, if you are using the default factory), so aborting the thread cannot affect the tasks.任务正在 ThreadPool 上执行(至少,如果您使用的是默认工厂),因此中止线程不会影响任务。 For aborting tasks, see Task Cancellation on msdn.有关中止任务,请参阅 msdn 上的任务取消

I tried CancellationTokenSource but i can't do this.我试过CancellationTokenSource但我不能这样做。 And i did do this with my own way.我确实用我自己的方式做到了这一点。 And it works.它有效。

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

And another class of the calling the method:以及调用该方法的另一类:

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

You can abort a task like a thread if you can cause the task to be created on its own thread and call Abort on its Thread object.如果可以使任务在其自己的线程上创建并在其Thread对象上调用Abort ,则可以像线程一样中止任务。 By default, a task runs on a thread pool thread or the calling thread - neither of which you typically want to abort.默认情况下,任务在线程池线程或调用线程上运行 - 您通常不想中止这两个线程。

To ensure the task gets its own thread, create a custom scheduler derived from TaskScheduler .为确保任务获得自己的线程,请创建一个派生自TaskScheduler的自定义调度程序。 In your implementation of QueueTask , create a new thread and use it to execute the task.QueueTask的实现中,创建一个新线程并使用它来执行任务。 Later, you can abort the thread, which will cause the task to complete in a faulted state with a ThreadAbortException .稍后,您可以中止线程,这将导致任务在出现ThreadAbortException的错误状态下完成。

Use this task scheduler:使用此任务调度程序:

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
}

Start your task like this:像这样开始你的任务:

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

Later, you can abort with:稍后,您可以中止:

scheduler.TaskThread.Abort();

Note that the caveat about aborting a thread still applies:请注意, 关于中止线程警告仍然适用:

The Thread.Abort method should be used with caution.应谨慎使用Thread.Abort方法。 Particularly when you call it to abort a thread other than the current thread, you do not know what code has executed or failed to execute when the ThreadAbortException is thrown, nor can you be certain of the state of your application or any application and user state that it is responsible for preserving.特别是当你调用它来中止当前线程以外的线程时,你不知道抛出ThreadAbortException时哪些代码已经执行或执行失败,也无法确定应用程序的状态或任何应用程序和用户状态它负责保存。 For example, calling Thread.Abort may prevent static constructors from executing or prevent the release of unmanaged resources.例如,调用Thread.Abort可能会阻止静态构造函数执行或阻止释放非托管资源。

You can use this class..: It works for all typs of returned Values..您可以使用此 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();
    }
}

To use it you can write:要使用它,你可以这样写:

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