25

背景

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

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

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

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

现在,我将代码移至 dotnet 核心- 如您所知,当您调用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 核心世界上,它似乎正在走向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();
}

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

4

5 回答 5

13

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

于 2020-04-28T04:55:43.947 回答
11

在不中止的情况下,唯一的解决方案是经常轮询取消请求,所以在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

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

更新:

如果你足够自信/绝望,你可以使用ThreadEx.Abort调用Thread.AbortInternalby 反射的方法。虽然不能保证它将成为 .NET Core 中的长期解决方案。

虽然我不完全同意Thread.Abort过时,因为它是一个很好的最后机会工具,可以关闭你没有影响的例程,但我也同意不惜一切代价避免堕胎,因为它可以有讨厌的副作用。如果您是整个代码库的作者,则始终可以避免。

更新 2:

从那时起,它似乎AbortInternal已被删除。至少当前的 .NET Core 源不包含这样的方法。

于 2018-11-25T08:11:10.373 回答
4

您可以使用 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 更干净。

于 2022-01-17T14:03:49.977 回答
0

在最近的 GitHub 问题中引用dotnet/runtime 合作者的回答

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

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

如果要中止的线程正在执行 I/O,则可以通过切换到异步方法以及 async 和 await 关键字并将CancellationToken.

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

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

因此,.NET Core 及更高版本似乎正式没有等效的 API。

于 2022-02-18T18:59:53.387 回答
0

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.");
  }
于 2022-02-01T07:30:29.400 回答