【问题标题】:How do I wrap a rogue function with a timeout?如何包装带有超时的流氓函数?
【发布时间】:2019-04-28 23:04:23
【问题描述】:

我在一个 3rd 方库中有一个函数,它偶尔会变得流氓并且永远不会返回。像这样:

    // This is 3rd party function so I can't make it take a cancellation token.
    public void RogueFunction()
    {

        while (true)
        {
            _logger.LogInformation("sleeping...");
            Thread.Sleep(100);
        }
    }

我想将它包装在一个带有超时的任务中,这很容易使用“task.Wait(mills)”来完成。虽然这会在超时后将控制权交还给我,但它实际上并没有终止任务。

在下面的代码中,rogue 函数在超时后继续记录。

    [Fact]
    public void Test()
    {
        var task = Task.Factory.StartNew(RogueFunction);
        var complete = task.Wait(500);
        if (!complete)
        {
            // how do I kill the task so that it quits logging?
            Thread.Sleep(5000);

            task.Dispose();  // Throws exception: A task may only be disposed if it is in a completion state (RanToCompletion, Faulted or Canceled).
        }
    }

我如何完全终止这个任务,这样我就可以重试它,而不会导致一堆它们在我的后台线程池中无限运行。

【问题讨论】:

  • 使用取消令牌在设定的时间后取消任务
  • 很遗憾,取消令牌与 task.Wait(timeout) 的效果相同。

标签: c# task


【解决方案1】:

看来Thread.Abort 是您唯一的选择。如果您担心这样做可能会使应用程序处于损坏状态(打开文件句柄等),那么最安全的选择是在不同的进程中运行线程,然后kill the process。另一个可行的解决方案是在不同的 AppDomain 中运行线程,然后中止线程并unload the AppDomain

【讨论】:

  • 嗯.. 我试图避免这种情况,但感谢您确认我没有从基于任务的方法中遗漏任何东西。
  • 是的,据我所知,任务是基于合作的原则。当事情变得流氓时,没有礼貌的方式让它表现出来。
  • @TheodorZoulias - 我会选择“不同的过程”选项,因为这是唯一安全的方法。即使在调用 Thread.Abort() 时使用单独的 AppDomain 也不安全 - 它也会破坏调用 AppDomain
  • @Enigmativity 同意。 Thread.Abort 是最容易实现的。有时您可以确定中止线程是安全的,因为您可以检查源代码并断言没有非托管资源会泄漏。
【解决方案2】:

更新

让一个有这样的功能:

static class Rogue
{
    // This is 3rd party function so I can't make it take a cancellation token.
    public static void RogueFunction()
    {
        while (true)
        {
            Console.WriteLine("RogueFunction works");
            Thread.Sleep(1000);
        }
    }
}

可能的解决方案是用这样的类包装它:

public class InfiniteAction
{
    private readonly Action action;
    private CancellationTokenSource cts;
    private Thread thread;

    public InfiniteAction(Action action) => this.action = action;

    public void Join() => thread?.Join();

    public void Start()
    {
        if (cts == null)
        {
            cts = new CancellationTokenSource();
            thread = new Thread(() => action());
            thread.IsBackground = true;
            thread.Start();
            cts.Token.Register(thread.Abort);
        }
    }

    public void Stop()
    {
        if (cts != null)
        {
            cts.Cancel();
            cts.Dispose();
            cts = null;
        }
    }
}

现在可以像InfiniteAction.Start() 一样开始无限动作并像InfiniteAction.Stop() 那样停止它。

可以手动完成:

void ManualCancelation()
{
    var infiniteAction = new InfiniteAction(Rogue.RogueFunction);
    Console.WriteLine("RogueFunction is executing.");
    infiniteAction.Start();

    Console.WriteLine("Press any key to stop it.");
    Console.ReadKey();
    Console.WriteLine();
    infiniteAction.Stop();

    Console.WriteLine("Make sure it has stopped and press any key to exit.");
    Console.ReadKey();
    Console.WriteLine();
}

或按计时器:

void ByTimerCancelation()
{
    var interval = 3000;
    var infiniteAction = new InfiniteAction(Rogue.RogueFunction);
    Console.WriteLine($"RogueFunction is executing and will be stopped in {interval} ms.");
    Console.WriteLine("Make sure it has stopped and press any key to exit.");
    infiniteAction.Start();
    var timer = new Timer(StopInfiniteAction, infiniteAction, interval, -1);
    Console.ReadKey();
    Console.WriteLine();
}

private void StopInfiniteAction(object action)
{
    var infiniteAction = action as InfiniteAction;
    if (infiniteAction != null)
        infiniteAction.Stop();
    else
        throw new ArgumentException($"Invalid argument {nameof(action)}");
}

【讨论】:

  • 调用Thread.Abort() 是不安全的。唯一应该调用它的时候是当你试图强行退出应用程序时。
  • 除非代码非常明显,否则仅代码的答案也会受到反对。我认为你需要一些严肃的解释。
  • @Enigmativity,在这种情况下,它是一个封装在一个类中的后台线程,所以它不是那么邪恶,因为它是唯一可能的解决方案(这里我同意 Theodor Zoulias 的回答 stackoverflow.com/a/55885754/10958092)。你有什么建议?
  • @Enigmativity,我将代码分成几部分。每个都应该很明显。
  • @Alex 您的代码中存在竞争条件。 CancellationTokenSource 可以在线程启动之前取消,导致尚未启动的线程中止,从而导致ThreadStartException。你最好切换命令cts.Token.Register(t.Abort)t.Start()的顺序。您还可以在InfiniteAction 类中保留对线程的引用,以便您可以在Join 使用cts.Cancel() 之后的中止线程。
猜你喜欢
  • 1970-01-01
  • 2012-12-01
  • 2020-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-28
  • 2014-08-06
  • 2013-10-18
相关资源
最近更新 更多