我有一系列代码块耗时过长。当它失败时,我不需要任何技巧。事实上,我想在这些 block 花费太长时间时抛出一个异常,并通过我们的标准错误处理来解决。我宁愿不从每个 block 中创建方法(这是迄今为止我看到的唯一建议),因为这需要对代码库进行重大重写。
如果可能的话,这是我喜欢创建的内容。
public void MyMethod( ... )
{
...
using (MyTimeoutObject mto = new MyTimeoutObject(new TimeSpan(0,0,30)))
{
// Everything in here must complete within the timespan
// or mto will throw an exception. When the using block
// disposes of mto, then the timer is disabled and
// disaster is averted.
}
...
}
我已经使用 Timer 类创建了一个简单的对象来执行此操作。 (对于那些喜欢复制/粘贴的人请注意:此代码不起作用!!)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
public class MyTimeoutObject : IDisposable
{
private Timer timer = null;
public MyTimeoutObject (TimeSpan ts)
{
timer = new Timer();
timer.Elapsed += timer_Elapsed;
timer.Interval = ts.TotalMilliseconds;
timer.Start();
}
void timer_Elapsed(object sender, ElapsedEventArgs e)
{
throw new TimeoutException("A code block has timed out.");
}
public void Dispose()
{
if (timer != null)
{
timer.Stop();
}
}
}
它不起作用,因为 System.Timers.Timer 类捕获、吸收并忽略其中抛出的任何异常,正如我发现的那样,这破坏了我的设计。在不进行全面重新设计的情况下,还有其他创建此类/功能的方法吗?
这在两个小时前看起来很简单,但让我很头疼。
最佳答案
好的,我已经在这个问题上花了一些时间,我想我有一个解决方案可以为您工作,而无需对您的代码进行太多更改。
以下是您将如何使用 Timebox
我创建的类。
public void MyMethod( ... ) {
// some stuff
// instead of this
// using(...){ /* your code here */ }
// you can use this
var timebox = new Timebox(TimeSpan.FromSeconds(1));
timebox.Execute(() =>
{
/* your code here */
});
// some more stuff
}
下面是 Timebox
的工作原理。
Timebox
对象是用给定的Timespan
创建的- 当
Execute
被调用时,Timebox
创建一个子AppDomain
来保存一个TimeboxRuntime
对象引用,并且返回一个代理给它 - 子
AppDomain
中的TimeboxRuntime
对象将Action
作为在子域中执行的输入 Timebox
然后创建一个任务来调用TimeboxRuntime
代理- 任务开始( Action 开始执行),“主”线程等待给定的
TimeSpan
- 在给定的
TimeSpan
之后(或任务完成时),无论Action
是否完成,子AppDomain
都会被卸载。 - 如果
action
超时,则抛出TimeoutException
,否则如果action
抛出异常,则由子AppDomain 捕获
并返回调用AppDomain
抛出
缺点是您的程序需要足够的权限才能创建AppDomain
。
这是一个示例程序,它演示了它是如何工作的(我相信你可以复制粘贴它,如果你包含正确的 using
)。我还创建了 this gist如果你有兴趣。
public class Program
{
public static void Main()
{
try
{
var timebox = new Timebox(TimeSpan.FromSeconds(1));
timebox.Execute(() =>
{
// do your thing
for (var i = 0; i < 1000; i++)
{
Console.WriteLine(i);
}
});
Console.WriteLine("Didn't Time Out");
}
catch (TimeoutException e)
{
Console.WriteLine("Timed Out");
// handle it
}
catch(Exception e)
{
Console.WriteLine("Another exception was thrown in your timeboxed function");
// handle it
}
Console.WriteLine("Program Finished");
Console.ReadLine();
}
}
public class Timebox
{
private readonly TimeSpan _ts;
public Timebox(TimeSpan ts)
{
_ts = ts;
}
public void Execute(Action func)
{
AppDomain childDomain = null;
try
{
// Construct and initialize settings for a second AppDomain. Perhaps some of
// this is unnecessary but perhaps not.
var domainSetup = new AppDomainSetup()
{
ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
ApplicationName = AppDomain.CurrentDomain.SetupInformation.ApplicationName,
LoaderOptimization = LoaderOptimization.MultiDomainHost
};
// Create the child AppDomain
childDomain = AppDomain.CreateDomain("Timebox Domain", null, domainSetup);
// Create an instance of the timebox runtime child AppDomain
var timeboxRuntime = (ITimeboxRuntime)childDomain.CreateInstanceAndUnwrap(
typeof(TimeboxRuntime).Assembly.FullName, typeof(TimeboxRuntime).FullName);
// Start the runtime, by passing it the function we're timboxing
Exception ex = null;
var timeoutOccurred = true;
var task = new Task(() =>
{
ex = timeboxRuntime.Run(func);
timeoutOccurred = false;
});
// start task, and wait for the alloted timespan. If the method doesn't finish
// by then, then we kill the childDomain and throw a TimeoutException
task.Start();
task.Wait(_ts);
// if the timeout occurred then we throw the exception for the caller to handle.
if(timeoutOccurred)
{
throw new TimeoutException("The child domain timed out");
}
// If no timeout occurred, then throw whatever exception was thrown
// by our child AppDomain, so that calling code "sees" the exception
// thrown by the code that it passes in.
if(ex != null)
{
throw ex;
}
}
finally
{
// kill the child domain whether or not the function has completed
if(childDomain != null) AppDomain.Unload(childDomain);
}
}
// don't strictly need this, but I prefer having an interface point to the proxy
private interface ITimeboxRuntime
{
Exception Run(Action action);
}
// Need to derive from MarshalByRefObject... proxy is returned across AppDomain boundary.
private class TimeboxRuntime : MarshalByRefObject, ITimeboxRuntime
{
public Exception Run(Action action)
{
try
{
// Nike: just do it!
action();
}
catch(Exception e)
{
// return the exception to be thrown in the calling AppDomain
return e;
}
return null;
}
}
}
编辑:
我使用 AppDomain
而不是 Thread
或 Task
的原因是因为没有用于终止的防弹方法任意代码的 Thread
或 Task
[ 1 ][ 2 ][ 3 ].根据您的要求,AppDomain 对我来说似乎是最好的方法。
关于c# - 如何为各种代码块创建通用超时对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34597178/