【问题标题】:.NET Core equivalent to Thread.Abort.NET Core 等效于 Thread.Abort
【发布时间】:2022-02-19 02:59:53
【问题描述】:

背景

我有一个Service 抽象。每个服务都有自己的WorkItem。 WorkItem 能够从一些数据开始。该服务正在限制WorkItem 的执行时间。假设单个工作项最多可能需要 60 秒。在此之后,Service 应该杀死它。

此代码从 .NET Framework 迁移,我创建了一个运行 Start(model) 方法的 Thread 对象。然后代码是这样的:

Thread t = new Thread(workItem.Start, model);
t.start();
if (!t.Join(TimeSpan.FromSeconds(60)))
    t.Abort();

Thread.Abort 正在为正在运行的线程注入异常,导致它立即停止。

现在,我将代码移至 dotnet core - 您可能知道,当您调用 Thread.Abort() 时,您会收到以下消息:

System.PlatformNotSupportedException: Thread abort is not supported on this platform.
   at System.Threading.Thread.Abort()
   at ...

目标

我想将WorkItem 的执行时间限制为特定的时间量。请注意,如果您运行这样的代码行,此限制也应该起作用:

Thread.Sleep(61000); // 61 seconds. should be stop after 60 seconds.

进展

在 dotnet core 世界上,似乎要转到Task 相关解决方案。所以,我想用CancellationToken。但它似乎不可能观看“取消”事件并立即停止。我看到的例子是使用while (!canceled) 循环,它不能停止长时间的操作(比如Thread.Sleep(1000000)

问题

怎么做才对?

更新

我写了这个示例代码:

public static bool ExecuteWithTimeLimit(TimeSpan timeSpan, Action codeBlock)
{
    try
    {
        Task task = Task.Factory.StartNew(() => codeBlock());
        if (!task.Wait(timeSpan))
        {
            // ABORT HERE!
            Console.WriteLine("Time exceeded. Aborted!");
        }
        return task.IsCompleted;
    }
    catch (AggregateException ae)
    {
        throw ae.InnerExceptions[0];
    }
}

还有这个Main 文件:

public static void Main(string[] args)
{
    bool Completed = ExecuteWithTimeLimit(TimeSpan.FromMilliseconds(2000), () =>
    {
        Console.WriteLine("start");
        Thread.Sleep(3000);
        Console.WriteLine("end");
    });

    Console.WriteLine($"Completed={Completed}");
    Console.ReadLine();
}

预期:“end”不会打印到屏幕上。实际:打印“结束”。有没有可以杀死Task的替代方法?

【问题讨论】:

  • 试试Task.Delay而不是Thread.Sleep
  • 您知道要正确执行此操作,您只需不中止线程...从一开始就不是好主意。可能应该作为 stackoverflow.com/questions/39243016/… 的副本关闭
  • @H.G.Sandhagen 它不会帮助处理实际上长时间占用 CPU 而不是休眠的代码......或者使用同步网络调用......
  • @H.G.Sandhagen - Thread.Sleep 只是一个例子。我可以想到其他不支持异步模型的示例。再比如:下载WebClient的文件,服务器真的很慢,所以耗时太长(超过限制)。
  • @AlexeiLevenkov - 不确定它是否“聪明”的想法。当您处理导致不同执行时间的不同类型的数据时,这是需要的。 Thread.Abort 的替代方法是为每个 WorkItem 创建一个单独的进程,如果运行时间过长,则将其终止 - 对我来说听起来会消耗太多资源。

标签: c# multithreading .net-core .net-5 thread-abort


【解决方案1】:

使用线程。中断();而不是 Abort 方法。

【讨论】:

  • 这在某些情况下可能很有用。
  • 这确实需要解释一下。
【解决方案2】:

在不中止的情况下,唯一的解决方案是经常轮询取消请求,所以在您提到的所有while (!canceled) 解决方案之后。

我看到的例子是使用while (!canceled) 循环,它不能停止长时间的操作(比如Thread.Sleep(1000000)

这只是部分正确。例如,可以像这样重写它以响应:

 var timeout = TimeSpan.FromSeconds(60);
 var stopwatch = new Stopwatch();
 stopwatch.Start();

 while (!cancelToken.IsCancellationRequested
  && stopwatch.ElapsedMilliseconds < timeout)
{
    Thread.Sleep(10);
}

当然,并不是每个任务都可以像这样轻松地重写来轮询取消。如果您处于深度调用链中,则检查每个级别的取消可能会很痛苦。出于这个原因,您还可以使用CancellationToken.ThrowIfCancellationRequested 方法,如果有取消请求,它将抛出OperationCanceledException。我通常不会只为自己抛出异常并将其用于控制​​流,但取消是合理的领域之一。

Abort相比,这个解决方案当然有一些限制:

  • 您将无法取消不支持取消且您无法重构它们的第 3 方例程
  • OperationCanceledException 很容易被吞下,而ThreadAbortException 总是在catch 块的末尾重新生成,因此即使包含一般捕获块,第 3 部分库也很有可能被中止。

更新:

如果您足够自信/绝望,您可以使用ThreadEx.Abort 方法,该方法通过反射调用Thread.AbortInternal。虽然不能保证它会成为 .NET Core 中长期存在的解决方案。

虽然我不完全同意让Thread.Abort 过时,因为它是一个很好的最后机会工具,可以关闭你没有影响的例程,但我也支持必须避免流产不惜一切代价,因为它可以拥有nasty side effects。如果您是整个代码库的作者,则始终可以避免。

更新 2:

似乎AbortInternal 从那时起已被删除。至少current .NET Core source不包含这样的方法。

【讨论】:

  • dotnet core 2.1 中似乎没有 Thread.InternalAbort()。有其他选择吗?
  • 它似乎对我不起作用。我看不到 AbortInternal 使用反射。你在 2.1 中看到了吗?
  • 你说得对,current .NET Core中没有AbortInternal
【解决方案3】:

您可以使用 Thread.Interrupt(),它会在工作线程中导致 ThreadInterruptedException()。您可以使用 try catch 捕获异常,然后将线程安全地加入主线程以清理工作线程。这看起来像这样:

Thread t = new Thread(workItem.Start, model);
t.start();

// do other stuff or wait

t.interrupt();
t.join();

而工作线程的功能是这样的:

try
{
   // stuff the worker thread needs to do
}
catch (Exception e)
{
   // go in here when interrupted
}

然后可以像这样实现等待

Thread t = new Thread(workItem.Start, model);
t.start();
if (!t.Join(TimeSpan.FromSeconds(60)))
{
    t.Interrupt();
    t.Join();
}

这是一种杀死线程的方法,但使用 CancelationTokens 会更干净。

【讨论】:

  • 这不能按预期工作。放置一行“Enumerable.Range(0, int.MaxValue).Distinct().ToList();”在 try-Block 中,您会看到线程将继续。
  • 来自Microsoft "如果这个线程当前没有被阻塞在等待、睡眠或加入状态,那么它会在下一次开始阻塞时被中断"。如果您使用取消标记,那段代码也会发生同样的事情,因为只有当程序员这样说时才会检查取消标记。但是就像我在答案中所说的那样,这不是最好的解决方案,并且取消令牌要好得多,但它可以正常工作。
  • 是的,但这使得解决方案不等同于 thread.Abort() - 您不能在完成之前中止原子的长时间运行操作。
  • 取消令牌也可以这样说,它们不是等价的,也没有与 thread.Abort 等价的解决方案。
【解决方案4】:

Thread.Abort() 曾经在小心处理时工作。没有讨论:Thread.Abort() 是一个危险的 API,它在调用堆栈深处的任何随机点抛出 ThreadAbortException。尽管如此,生产日志显示,当仔细实施 Thread.Abort() 时,不会引发任何崩溃或状态损坏。

CancellationToken 是当今实现可取消操作的安全方式。但它不是 Thread.Abort() 的替代品:它只支持协作取消场景,可取消的处理负责定期检查是否已被取消。

if(cancelToken.IsCancellationRequested){
   throw new TaskCancelledException();
}

更新

正如@Theodor 所建议的,同样的结果可以通过使用来实现

cancelToken.ThrowIfCancellationRequested();

这个函数实现的逻辑和上面一样

然后您可以按如下方式处理抛出的异常

try
  {
    await YourTask(cancellationToken);
  }
  catch (OperationCanceledException ex) // includes TaskCanceledException
  {
    MessageBox.Show("Your submission was canceled.");
  }

【讨论】:

    【解决方案5】:

    dotnet/runtime 合作者在recent GitHub issue 中引用an answer

    在不询问线程的情况下中止线程是一种非常危险的做法,并且自 .NET Core 1.0 以来,现代 .NET 的设计就不再支持它们,这就是我将关闭此问题的原因。

    安全的替代方法是在您的线程中传递CancellationTokens,并自己通过调用CancellationToken.ThrowIfCancellationRequested 标记可以中止的点。一个很好的经验法则是在循环开始时调用此方法,但也许不是所有循环,你必须做出平衡;如果调用太频繁,性能会下降,如果调用太少,代码可能不会立即停止。

    如果您要中止的线程正在执行 I/O,您可以通过切换到异步方法以及 async 和 await 关键字并将 CancellationToken 传递给它们来取消它。

    但无论哪种方式,您的代码都需要重构。

    (teo-tsirpanis,2022 年 2 月 18 日)

    所以在.NET Core 及更高版本中似乎官方没有等效的API。

    【讨论】:

      猜你喜欢
      • 2018-12-21
      • 1970-01-01
      • 2019-04-12
      • 2021-06-03
      • 2018-03-05
      • 1970-01-01
      • 1970-01-01
      • 2019-11-12
      • 2017-07-23
      相关资源
      最近更新 更多