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()
之后加入被中止的线程。