我有一系列代码块花了太长时间.它失败时我不需要任何技巧.事实上,我想在这些块花费太长时间时抛出异常,并且只是通过我们的标准错误处理而失败.我宁愿不为每个块创建方法(这是我到目前为止看到的唯一建议),因为它需要重写代码库.
如果可能的话,这就是我想要创建的内容.
public void MyMethod( ... ) { ... using (MyTimeoutObject mto = new MyTimeoutObject(new TimeSpan(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的工作原理.
>使用给定的Timespan创建TimeBox对象
>调用Execute时,TimeBox会创建一个子AppDomain来保存TimeBoxRuntime对象引用,并向其返回一个代理
>子AppDomain中的TimeBoxRuntime对象将Action作为输入在子域中执行
> TimeBox然后创建一个任务来调用TimeBoxRuntime代理
>任务已启动(并且操作执行开始),并且“main”线程等待给定的TimeSpan
>在给定的TimeSpan之后(或任务完成时),无论Action是否完成,都会卸载子AppDomain.
>如果操作超时,则抛出TimeoutException,否则如果操作抛出异常,则由子AppDomain捕获并返回以调用AppDomain
缺点是您的程序需要足够高的权限才能创建AppDomain.
这是一个示例程序,演示它是如何工作的(我相信如果包含正确的用法,你可以复制粘贴它).如果你有兴趣,我也创建了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而不是仅使用线程或任务,是因为没有用于终止任意代码的线程或任务的防弹方式[2] [3].根据您的要求,AppDomain对我来说似乎是最好的方法.