如果同步方法执行时间太长,我正在寻找一种有效的方法来抛出超时异常。我见过一些样品,但没有什么能做到我想要的。
我需要做的是
如果执行时间太长,不必须终止同步方法。 (多次故障会使断路器跳闸并防止级联故障)
到目前为止我的解决方案如下所示。请注意,我确实将CancellationToken传递给了sync方法,希望它能在超时时遵循取消请求。此外,我的解决方案返回一个任务,然后我可以根据需要等待我的调用代码。
我担心的是,这个代码会为每个监控方法创建两个任务。我认为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;
}
修改
使用@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;
}
答案 0 :(得分:18)
如果您有一个名为Task
的{{1}},则可以执行以下操作:
task
如果var delay = Task.Delay(TimeSpan.FromSeconds(3));
var timeoutTask = Task.WhenAny(task, delay);
最终成为timeoutTask.Result
,那么它就不会超时。否则,它是task
并且它确实超时。
我不知道这是否会与您实施的内容完全相同,但这是内置的方法。
答案 1 :(得分:2)
我已为.NET 4.0
重新编写了此解决方案,其中某些方法不可用,例如Delay
。此版本正在监视返回object
的方法。如何在Delay
中实施.NET 4.0
来自此处:How to put a task to sleep (or delay) in C# 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();
}
答案 2 :(得分:2)
详细阐述Timothy Shields的清洁解决方案:
if (task == await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(3))))
{
return await task;
}
else
throw new TimeoutException();
我发现,这个解决方案也将处理Task有一个返回值的情况 - 即:
async Task<T>
答案 3 :(得分:0)
贾斯帕的回答让我大部分时间,但我特别想要一个 void 函数来调用超时的非任务同步方法。这就是我最终得到的结果:
public static void RunWithTimeout(Action action, TimeSpan timeout)
{
var task = Task.Run(action);
try
{
var success = task.Wait(timeout);
if (!success)
{
throw new TimeoutException();
}
}
catch (AggregateException ex)
{
throw ex.InnerException;
}
}
称之为:
RunWithTimeout(() => File.Copy(..), TimeSpan.FromSeconds(3));