Мониторинг синхронного метода таймаута
Я ищу эффективный способ выбросить исключение тайм-аута, если синхронный метод слишком долго выполняется. Я видел несколько примеров, но ничего не делает, что я хочу.
Что мне нужно сделать, это
- Убедитесь, что метод синхронизации превышает SLA
- Если он генерирует исключение тайм-аута
Я выполняю не, чтобы завершить метод синхронизации, если он выполняется слишком долго. (Несколько отказов отключат автоматический выключатель и предотвратит каскадный отказ)
Мое решение пока показано ниже. Обратите внимание, что я передаю метод CancellationToken методу синхронизации в надежде, что он выполнит запрос на отмену таймаута. Также мое решение возвращает задачу, которую можно ожидать по иному, по желанию, с помощью моего кода вызова.
Меня беспокоит, что этот код создает две задачи для каждого метода, который отслеживается. Я думаю, что TPL справится с этим хорошо, но я хотел бы подтвердить.
Это имеет смысл? Есть ли лучший способ сделать это?
private Task TimeoutSyncMethod( Action<CancellationToken> syncAction, TimeSpan timeout )
{
var cts = new CancellationTokenSource();
var outer = Task.Run( () =>
{
try
{
//Start the synchronous method - passing it a cancellation token
var inner = Task.Run( () => syncAction( cts.Token ), cts.Token );
if( !inner.Wait( timeout ) )
{
//Try give the sync method a chance to abort grecefully
cts.Cancel();
//There was a timeout regardless of what the sync method does - so throw
throw new TimeoutException( "Timeout waiting for method after " + timeout );
}
}
finally
{
cts.Dispose();
}
}, cts.Token );
return outer;
}
Edit:
Используя @Timothy ответ, я теперь использую это. Хотя не намного меньше кода, это намного яснее. Спасибо!
private Task TimeoutSyncMethod( Action<CancellationToken> syncAction, TimeSpan timeout )
{
var cts = new CancellationTokenSource();
var inner = Task.Run( () => syncAction( cts.Token ), cts.Token );
var delay = Task.Delay( timeout, cts.Token );
var timeoutTask = Task.WhenAny( inner, delay ).ContinueWith( t =>
{
try
{
if( !inner.IsCompleted )
{
cts.Cancel();
throw new TimeoutException( "Timeout waiting for method after " + timeout );
}
}
finally
{
cts.Dispose();
}
}, cts.Token );
return timeoutTask;
}
Ответы
Ответ 1
Если у вас есть Task
, называемый Task
, вы можете сделать это:
var delay = Task.Delay(TimeSpan.FromSeconds(3));
var timeoutTask = Task.WhenAny(task, delay);
Если timeoutTask.Result
заканчивается Task
, то он не истекает. В противном случае он delay
, и он сделал тайм-аут.
Я не знаю, будет ли это вести себя идентично тому, что вы реализовали, но это встроенный способ сделать это.
Ответ 2
Я переписал это решение для .NET 4.0
, где некоторые методы недоступны, например Delay
. Эта версия контролирует метод, который возвращает object
. Как реализовать Delay
в .NET 4.0
отсюда: Как поставить задачу в режим ожидания (или задержки) на С# 4.0?
public class OperationWithTimeout
{
public Task<object> Execute(Func<CancellationToken, object> operation, TimeSpan timeout)
{
var cancellationToken = new CancellationTokenSource();
// Two tasks are created.
// One which starts the requested operation and second which starts Timer.
// Timer is set to AutoReset = false so it runs only once after given 'delayTime'.
// When this 'delayTime' has elapsed then TaskCompletionSource.TrySetResult() method is executed.
// This method attempts to transition the 'delayTask' into the RanToCompletion state.
Task<object> operationTask = Task<object>.Factory.StartNew(() => operation(cancellationToken.Token), cancellationToken.Token);
Task delayTask = Delay(timeout.TotalMilliseconds);
// Then WaitAny() waits for any of the provided task objects to complete execution.
Task[] tasks = new Task[]{operationTask, delayTask};
Task.WaitAny(tasks);
try
{
if (!operationTask.IsCompleted)
{
// If operation task didn't finish within given timeout call Cancel() on token and throw 'TimeoutException' exception.
// If Cancel() was called then in the operation itself the property 'IsCancellationRequested' will be equal to 'true'.
cancellationToken.Cancel();
throw new TimeoutException("Timeout waiting for method after " + timeout + ". Method was to slow :-)");
}
}
finally
{
cancellationToken.Dispose();
}
return operationTask;
}
public static Task Delay(double delayTime)
{
var completionSource = new TaskCompletionSource<bool>();
Timer timer = new Timer();
timer.Elapsed += (obj, args) => completionSource.TrySetResult(true);
timer.Interval = delayTime;
timer.AutoReset = false;
timer.Start();
return completionSource.Task;
}
}
Как использовать его в приложении консоли.
public static void Main(string[] args)
{
var operationWithTimeout = new OperationWithTimeout();
TimeSpan timeout = TimeSpan.FromMilliseconds(10000);
Func<CancellationToken, object> operation = token =>
{
Thread.Sleep(9000); // 12000
if (token.IsCancellationRequested)
{
Console.Write("Operation was cancelled.");
return null;
}
return 123456;
};
try
{
var t = operationWithTimeout.Execute(operation, timeout);
var result = t.Result;
Console.WriteLine("Operation returned '" + result + "'");
}
catch (TimeoutException tex)
{
Console.WriteLine(tex.Message);
}
Console.WriteLine("Press enter to exit");
Console.ReadLine();
}
Ответ 3
Чтобы выработать чистое решение Timothy Shields:
if (task == await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(3))))
{
return await task;
}
else
throw new TimeoutException();
Это решение, которое я нахожу, также будет обрабатывать случай, когда задача имеет возвращаемое значение - i.e:
async Task<T>
Подробнее можно найти здесь: MSDN: Создание задачи. TimeoutAfter Method