Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/256.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何用超时包装恶意函数?_C#_Task - Fatal编程技术网

C# 如何用超时包装恶意函数?

C# 如何用超时包装恶意函数?,c#,task,C#,Task,我在一个第三方库中有一个函数,它偶尔会流氓,永远不会返回。大概是这样的: // This is 3rd party function so I can't make it take a cancellation token. public void RogueFunction() { while (true) { _logger.LogInformation("sleeping...");

我在一个第三方库中有一个函数,它偶尔会流氓,永远不会返回。大概是这样的:

    // 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).
        }
    }

如何完全终止此任务,以便我可以重试,而不必在我的后台线程池中无限地运行一堆任务。

这似乎是您唯一的选择。如果您担心这样做会使应用程序处于损坏状态(打开文件句柄等),那么最安全的选择是在不同的进程中运行线程,然后重试。另一个可行的解决方案是在不同的AppDomain中运行该线程,然后中止该线程并更新

让我们有这样一个功能:

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

使用取消令牌在设置的时间量后取消任务。很遗憾,取消令牌与任务具有相同的效果。等待(超时)。Hmm。。我试图避免这种情况,但感谢您确认我没有遗漏基于任务的方法。是的,据我所知,任务是基于合作原则的。当某件事情变得无赖时,没有礼貌的方式让它表现出来。@TheodorZoulias-我会选择“不同的过程”选项,因为这是唯一安全的方式。调用
线程时,即使使用单独的
AppDomain
也不安全。Abort()
-它也会损坏调用
AppDomain
。@Enigmativity同意。中止是最容易实现的。有时您可以确保中止线程是安全的,因为您可以检查源代码并断言非托管资源不会泄漏。调用
thread.Abort()
是不安全的。只有当你试图强制退出应用程序时才应该调用它。除非代码非常明显,否则只使用代码的答案也会被拒绝。我认为您需要一些认真的解释。@Enigmativity,在本例中,它是封装在类中的背景线程,因此它不是那么邪恶,因为它是唯一可能的解决方案(在这里,我同意Theodor Zoulias的回答)。你有什么建议?@Enigmativity,我把代码分成了几个部分。每个都应该非常明显。@Alex在代码中有一个竞争条件。
CancellationTokenSource
可能在线程启动之前被取消,导致尚未启动的线程中止,从而导致
ThreadStartException
。您最好切换命令的顺序
cts.Token.Register(t.Abort)
t.Start()
。您还可以在
InfiniteAction
类中保留线程的引用,以便
cts.Cancel()
之后加入被中止的线程。