【问题标题】:How do I abort/cancel TPL Tasks?如何中止/取消 TPL 任务?
【发布时间】:2011-01-24 15:42:16
【问题描述】:

在一个线程中,我创建了一些System.Threading.Task 并启动每个任务。

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

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

【问题讨论】:

标签: c# .net multithreading task abort


【解决方案1】:

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

【讨论】:

  • 很好的解释。我有一个问题,当我们在 Task.Factory.StartNew 中没有匿名方法时它是如何工作的?像 Task.Factory.StartNew(() => ProcessMyMethod(),cancellationToken)
  • 如果在执行任务中没有返回的阻塞调用怎么办?
  • @mehmet6parmak 我认为你唯一能做的就是使用Task.Wait(TimeSpan / int)从外部给它一个(基于时间的)截止日期。
  • 如果我有我的自定义类来管理新Task 中方法的执行怎么办?类似:public int StartNewTask(Action method)。在StartNewTask 方法中,我通过Task task = new Task(() => method()); task.Start(); 创建了一个新的Task。那么如何管理CancellationToken?我也想知道是否像Thread 我需要实现一个逻辑来检查是否有一些任务仍然挂起,所以当Form.Closing 时杀死它们。对于Threads,我使用Thread.Abort()
  • 天啊,多么糟糕的例子!这是一个简单的布尔条件,当然这是第一个尝试的!但是,即我在一个单独的任务中有一个函数,这可能需要很长时间才能结束,理想情况下它不应该知道任何关于线程或其他的事情。那么如何根据您的建议取消该功能?
【解决方案2】:

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

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() 来展示最常见的用例 - 使用旧单线程代码的任务的舒适性,它不使用 CancellationTokenSource 类来确定是否应该取消它。

【讨论】:

  • 感谢这个想法。使用这种方法实现了一些外部代码的超时,它没有CancellationToken 支持...
  • AFAIK thread.abort 会让你不知道你的堆栈,它可能是无效的。我从未尝试过,但我猜想在单独的应用程序域中启动一个线程,thread.abort 将被保存!此外,为了中止一项任务,您的解决方案中浪费了整个线程。您不必首先使用任务,而是使用线程。 (否决)
  • 正如我所写 - 此解决方案是在某些情况下可能会考虑的最后手段。当然应该考虑CancellationToken 或更简单的无竞争条件的解决方案。上面的代码只说明了方法,没有说明使用范围。
  • 我认为这种方法可能会产生未知的后果,我不会在生产代码中推荐它。任务并不总是保证在不同的线程中执行,这意味着如果调度程序决定,它们可以在与创建它们的线程相同的线程中运行(这意味着主线程将被传递给 thread 局部变量)。在您的代码中,您最终可能会中止主线程,这不是您真正想要的。如果您坚持中止,也许在中止之前检查线程是否相同是一个不错的想法
  • @Martin - 当我在 SO 上研究过这个问题时,我看到你对使用 Thread.Abort 来终止任务的几个答案投了反对票,但你没有提供任何替代解决方案。 如何杀死不支持取消且在任务中运行的第 3 方代码
【解决方案3】:

就像this post 建议的那样,这可以通过以下方式完成:

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

虽然可行,但不建议使用这种方法。如果您可以控制在任务中执行的代码,则最好对取消进行适当的处​​理。

【讨论】:

  • +1 用于在说明其后备方案时提供不同的方法。我不知道这可以做到:)
  • 感谢您的解决方案!我们可以将令牌传递给方法并取消令牌源,而不是以某种方式从方法中获取线程实例并直接中止该实例。
  • 被中止的任务线程可能是线程池线程或调用任务的调用者线程。为避免这种情况,您可以use a TaskScheduler to specify a dedicated thread for the task
  • .NET Framework 5.0+ 版本不再支持此功能
【解决方案4】:

这种事情是不推荐使用Abort 的逻辑原因之一。首先,尽可能不要使用Thread.Abort() 来取消或停止线程。 Abort() 应该只用于强制终止没有响应更和平的停止请求的线程及时。

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

【讨论】:

    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

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

      例如

      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】:

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

        此外,我建议您也将主线程更改为通过 CancellationToken 运行。打电话给Thread.Abort() 是个坏主意——它会导致各种难以诊断的问题。相反,该线程可以使用与您的任务相同的Cancellation - 并且相同的CancellationTokenSource 可用于触发取消所有您的任务和主线程。

        这将带来更简单、更安全的设计。

        【讨论】:

          【解决方案8】:

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

          【讨论】:

            【解决方案9】:

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

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

            【讨论】:

              【解决方案10】:

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

              【讨论】:

                【解决方案11】:

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

                【讨论】:

                  【解决方案12】:

                  如果您可以在自己的线程上创建任务并在其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();
                  

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

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

                  【讨论】:

                  • 此代码因运行时异常而失败:System.InvalidOperationException: RunSynchronously 可能不会在已启动的任务上调用。
                  • @TheodorZoulias 好收获。谢谢。我修复了代码并总体上改进了答案。
                  • 是的,这修复了错误。可能应该提到的另一个警告是 .NET Core 不支持 Thread.Abort。尝试在那里使用它会导致异常:System.PlatformNotSupportedException: Thread abort is not supported on this platform. 第三个警告是 SingleThreadTaskScheduler 不能有效地用于 promise 样式的任务,换句话说,使用async 代表创建的任务。例如一个嵌入的await Task.Delay(1000)在没有线程中运行,所以它不影响线程事件。
                  • 'SingleThreadTaskScheduler' 没有实现继承的抽象成员'TaskScheduler.TryExecuteTaskInline(Task, bool)
                  • @Toolkit 不错。最后一个方法的名称错误。它应该是TryExecuteTaskInline。我更新了代码 sn-p。
                  猜你喜欢
                  • 1970-01-01
                  • 2015-12-07
                  • 1970-01-01
                  • 2012-03-29
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多