实现 C# 通用超时
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/299198/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Implement C# Generic Timeout
提问by chilltemp
I am looking for good ideas for implementing a generic way to have a single line (or anonymous delegate) of code execute with a timeout.
我正在寻找实现一种通用方式的好主意,以使单行(或匿名委托)代码执行超时。
TemperamentalClass tc = new TemperamentalClass();
tc.DoSomething(); // normally runs in 30 sec. Want to error at 1 min
I'm looking for a solution that can elegantly be implemented in many places where my code interacts with temperamental code (that I can't change).
我正在寻找一种可以在我的代码与易变代码(我无法更改)交互的许多地方优雅地实现的解决方案。
In addition, I would like to have the offending "timed out" code stopped from executing further if possible.
此外,如果可能的话,我希望有问题的“超时”代码停止进一步执行。
采纳答案by TheSoftwareJedi
The really tricky part here was killing the long running task through passing the executor thread from the Action back to a place where it could be aborted. I accomplished this with the use of a wrapped delegate that passes out the thread to kill into a local variable in the method that created the lambda.
这里真正棘手的部分是通过将执行器线程从 Action 传递回可以中止的位置来终止长时间运行的任务。我通过使用包装的委托完成了这一点,该委托将要杀死的线程传递到创建 lambda 的方法中的局部变量中。
I submit this example, for your enjoyment. The method you are really interested in is CallWithTimeout. This will cancel the long running thread by aborting it, and swallowing the ThreadAbortException:
我提交这个例子,供您欣赏。您真正感兴趣的方法是 CallWithTimeout。 这将通过中止并吞下 ThreadAbortException 来取消长时间运行的线程:
Usage:
用法:
class Program
{
static void Main(string[] args)
{
//try the five second method with a 6 second timeout
CallWithTimeout(FiveSecondMethod, 6000);
//try the five second method with a 4 second timeout
//this will throw a timeout exception
CallWithTimeout(FiveSecondMethod, 4000);
}
static void FiveSecondMethod()
{
Thread.Sleep(5000);
}
The static method doing the work:
静态方法做的工作:
static void CallWithTimeout(Action action, int timeoutMilliseconds)
{
Thread threadToKill = null;
Action wrappedAction = () =>
{
threadToKill = Thread.CurrentThread;
try
{
action();
}
catch(ThreadAbortException ex){
Thread.ResetAbort();// cancel hard aborting, lets to finish it nicely.
}
};
IAsyncResult result = wrappedAction.BeginInvoke(null, null);
if (result.AsyncWaitHandle.WaitOne(timeoutMilliseconds))
{
wrappedAction.EndInvoke(result);
}
else
{
threadToKill.Abort();
throw new TimeoutException();
}
}
}
回答by Marc Gravell
Well, you could do things with delegates (BeginInvoke, with a callback setting a flag - and the original code waiting for that flag or timeout) - but the problem is that it is very hard to shut down the running code. For example, killing (or pausing) a thread is dangerous... so I don't think there is an easy way to do this robustly.
好吧,你可以用委托做一些事情(BeginInvoke,用回调设置一个标志 - 以及等待该标志或超时的原始代码) - 但问题是很难关闭正在运行的代码。例如,杀死(或暂停)一个线程是危险的......所以我认为没有一种简单的方法可以稳健地做到这一点。
I'll post this, but note it is not ideal - it doesn't stop the long-running task, and it doesn't clean up properly on failure.
我会发布这个,但请注意它并不理想 - 它不会停止长期运行的任务,并且不会在失败时正确清理。
static void Main()
{
DoWork(OK, 5000);
DoWork(Nasty, 5000);
}
static void OK()
{
Thread.Sleep(1000);
}
static void Nasty()
{
Thread.Sleep(10000);
}
static void DoWork(Action action, int timeout)
{
ManualResetEvent evt = new ManualResetEvent(false);
AsyncCallback cb = delegate {evt.Set();};
IAsyncResult result = action.BeginInvoke(cb, null);
if (evt.WaitOne(timeout))
{
action.EndInvoke(result);
}
else
{
throw new TimeoutException();
}
}
static T DoWork<T>(Func<T> func, int timeout)
{
ManualResetEvent evt = new ManualResetEvent(false);
AsyncCallback cb = delegate { evt.Set(); };
IAsyncResult result = func.BeginInvoke(cb, null);
if (evt.WaitOne(timeout))
{
return func.EndInvoke(result);
}
else
{
throw new TimeoutException();
}
}
回答by Jason Hymanson
I just knocked this out now so it might need some improvement, but will do what you want. It is a simple console app, but demonstrates the principles needed.
我现在刚刚解决了这个问题,所以它可能需要一些改进,但会做你想做的。这是一个简单的控制台应用程序,但演示了所需的原则。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace TemporalThingy
{
class Program
{
static void Main(string[] args)
{
Action action = () => Thread.Sleep(10000);
DoSomething(action, 5000);
Console.ReadKey();
}
static void DoSomething(Action action, int timeout)
{
EventWaitHandle waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
AsyncCallback callback = ar => waitHandle.Set();
action.BeginInvoke(callback, null);
if (!waitHandle.WaitOne(timeout))
throw new Exception("Failed to complete in the timeout specified.");
}
}
}
回答by Pop Catalin
This is how I'd do it:
这就是我要做的:
public static class Runner
{
public static void Run(Action action, TimeSpan timeout)
{
IAsyncResult ar = action.BeginInvoke(null, null);
if (ar.AsyncWaitHandle.WaitOne(timeout))
action.EndInvoke(ar); // This is necesary so that any exceptions thrown by action delegate is rethrown on completion
else
throw new TimeoutException("Action failed to complete using the given timeout!");
}
}
回答by George Tsiokos
Some minor changes to Pop Catalin's great answer:
对 Pop Catalin 的精彩回答进行了一些小改动:
- Func instead of Action
- Throw exception on bad timeout value
- Calling EndInvoke in case of timeout
- Func 代替 Action
- 对错误的超时值抛出异常
- 超时时调用 EndInvoke
Overloads have been added to support signaling worker to cancel execution:
添加了重载以支持通知工作人员取消执行:
public static T Invoke<T> (Func<CancelEventArgs, T> function, TimeSpan timeout) {
if (timeout.TotalMilliseconds <= 0)
throw new ArgumentOutOfRangeException ("timeout");
CancelEventArgs args = new CancelEventArgs (false);
IAsyncResult functionResult = function.BeginInvoke (args, null, null);
WaitHandle waitHandle = functionResult.AsyncWaitHandle;
if (!waitHandle.WaitOne (timeout)) {
args.Cancel = true; // flag to worker that it should cancel!
/* ?————————————————————————————————————————————————————————————————————————?
| IMPORTANT: Always call EndInvoke to complete your asynchronous call. |
| http://msdn.microsoft.com/en-us/library/2e08f6yc(VS.80).aspx |
| (even though we arn't interested in the result) |
?————————————————————————————————————————————————————————————————————————? */
ThreadPool.UnsafeRegisterWaitForSingleObject (waitHandle,
(state, timedOut) => function.EndInvoke (functionResult),
null, -1, true);
throw new TimeoutException ();
}
else
return function.EndInvoke (functionResult);
}
public static T Invoke<T> (Func<T> function, TimeSpan timeout) {
return Invoke (args => function (), timeout); // ignore CancelEventArgs
}
public static void Invoke (Action<CancelEventArgs> action, TimeSpan timeout) {
Invoke<int> (args => { // pass a function that returns 0 & ignore result
action (args);
return 0;
}, timeout);
}
public static void TryInvoke (Action action, TimeSpan timeout) {
Invoke (args => action (), timeout); // ignore CancelEventArgs
}
回答by Rinat Abdullin
We are using code like this heavily in production:
我们在生产中大量使用这样的代码:
var result = WaitFor<Result>.Run(1.Minutes(), () => service.GetSomeFragileResult());
Implementation is open-sourced, works efficiently even in parallel computing scenarios and is available as a part of Lokad Shared Libraries
实施是开源的,即使在并行计算场景中也能高效运行,并且作为Lokad 共享库的一部分提供
/// <summary>
/// Helper class for invoking tasks with timeout. Overhead is 0,005 ms.
/// </summary>
/// <typeparam name="TResult">The type of the result.</typeparam>
[Immutable]
public sealed class WaitFor<TResult>
{
readonly TimeSpan _timeout;
/// <summary>
/// Initializes a new instance of the <see cref="WaitFor{T}"/> class,
/// using the specified timeout for all operations.
/// </summary>
/// <param name="timeout">The timeout.</param>
public WaitFor(TimeSpan timeout)
{
_timeout = timeout;
}
/// <summary>
/// Executes the spcified function within the current thread, aborting it
/// if it does not complete within the specified timeout interval.
/// </summary>
/// <param name="function">The function.</param>
/// <returns>result of the function</returns>
/// <remarks>
/// The performance trick is that we do not interrupt the current
/// running thread. Instead, we just create a watcher that will sleep
/// until the originating thread terminates or until the timeout is
/// elapsed.
/// </remarks>
/// <exception cref="ArgumentNullException">if function is null</exception>
/// <exception cref="TimeoutException">if the function does not finish in time </exception>
public TResult Run(Func<TResult> function)
{
if (function == null) throw new ArgumentNullException("function");
var sync = new object();
var isCompleted = false;
WaitCallback watcher = obj =>
{
var watchedThread = obj as Thread;
lock (sync)
{
if (!isCompleted)
{
Monitor.Wait(sync, _timeout);
}
}
// CAUTION: the call to Abort() can be blocking in rare situations
// http://msdn.microsoft.com/en-us/library/ty8d3wta.aspx
// Hence, it should not be called with the 'lock' as it could deadlock
// with the 'finally' block below.
if (!isCompleted)
{
watchedThread.Abort();
}
};
try
{
ThreadPool.QueueUserWorkItem(watcher, Thread.CurrentThread);
return function();
}
catch (ThreadAbortException)
{
// This is our own exception.
Thread.ResetAbort();
throw new TimeoutException(string.Format("The operation has timed out after {0}.", _timeout));
}
finally
{
lock (sync)
{
isCompleted = true;
Monitor.Pulse(sync);
}
}
}
/// <summary>
/// Executes the spcified function within the current thread, aborting it
/// if it does not complete within the specified timeout interval.
/// </summary>
/// <param name="timeout">The timeout.</param>
/// <param name="function">The function.</param>
/// <returns>result of the function</returns>
/// <remarks>
/// The performance trick is that we do not interrupt the current
/// running thread. Instead, we just create a watcher that will sleep
/// until the originating thread terminates or until the timeout is
/// elapsed.
/// </remarks>
/// <exception cref="ArgumentNullException">if function is null</exception>
/// <exception cref="TimeoutException">if the function does not finish in time </exception>
public static TResult Run(TimeSpan timeout, Func<TResult> function)
{
return new WaitFor<TResult>(timeout).Run(function);
}
}
This code is still buggy, you can try with this small test program:
这段代码还是有问题,你可以试试这个小测试程序:
static void Main(string[] args) {
// Use a sb instead of Console.WriteLine() that is modifying how synchronous object are working
var sb = new StringBuilder();
for (var j = 1; j < 10; j++) // do the experiment 10 times to have chances to see the ThreadAbortException
for (var ii = 8; ii < 15; ii++) {
int i = ii;
try {
Debug.WriteLine(i);
try {
WaitFor<int>.Run(TimeSpan.FromMilliseconds(10), () => {
Thread.Sleep(i);
sb.Append("Processed " + i + "\r\n");
return i;
});
}
catch (TimeoutException) {
sb.Append("Time out for " + i + "\r\n");
}
Thread.Sleep(10); // Here to wait until we get the abort procedure
}
catch (ThreadAbortException) {
Thread.ResetAbort();
sb.Append(" *** ThreadAbortException on " + i + " *** \r\n");
}
}
Console.WriteLine(sb.ToString());
}
}
There is a race condition. It is clearly possible that a ThreadAbortException gets raised after the method WaitFor<int>.Run()
is being called. I didn't find a reliable way to fix this, however with the same test I cannot repro any problem with the TheSoftwareJediaccepted answer.
存在竞争条件。很明显,WaitFor<int>.Run()
在调用方法后会引发 ThreadAbortException 。我没有找到解决此问题的可靠方法,但是通过相同的测试,我无法重现TheSoftwareJedi接受的答案的任何问题。
回答by Rinat Abdullin
What about using Thread.Join(int timeout)?
使用 Thread.Join(int timeout) 怎么样?
public static void CallWithTimeout(Action act, int millisecondsTimeout)
{
var thread = new Thread(new ThreadStart(act));
thread.Start();
if (!thread.Join(millisecondsTimeout))
throw new Exception("Timed out");
}