Как обернуть мошенническую функцию тайм-аутом?
У меня есть функция в сторонней библиотеке, которая иногда выходит из строя и никогда не возвращается. Как то так:
// 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)". Хотя это возвращает мне контроль после тайм-аута, на самом деле это не убивает задачу.
В приведенном ниже коде мошенническая функция продолжает регистрироваться после истечения времени ожидания.
[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).
}
}
Как мне полностью убить эту задачу, чтобы я мог повторить ее, не заканчивая кучей из них, бесконечно работающих в моем фоновом пуле потоков.
2 ответа
Кажется, что Thread.Abort
это ваш единственный вариант. Если вы боитесь, что это может привести к тому, что приложение будет повреждено (дескрипторы открытых файлов и т. Д.), Тогда самый безопасный вариант - запустить поток в другом процессе, а затем завершить процесс. Другое работоспособное решение - запустить поток в другом домене приложений, а затем прервать поток и выгрузить домен приложений.
ОБНОВЛЕНО:
Пусть у человека есть такая функция:
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)}");
}