如何为各种代码块创建通用超时对象?
我有一系列代码块耗时太长。 失败时我不需要任何技巧。 事实上,当这些块花费太长时间时,我想抛出一个异常,并通过我们的标准错误处理而退出。 我宁愿不从每个块中创建方法(这是我目前看到的唯一建议),因为它需要对代码库进行重大改写。
这是我想如果可能的创建。
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
代理 TimeSpan
TimeSpan
(或任务完成时),无论Action
是否完成,子AppDomain
被卸载。 action
超时,则引发TimeoutException
;否则,如果action
引发异常,则由子AppDomain
捕获并由调用的AppDomain
返回 缺点是您的程序需要提升足够的权限才能创建AppDomain
。
下面是一个示例程序,演示了它的工作原理(如果包含正确的using
,我相信您可以复制粘贴该内容)。 如果您有兴趣,我也创建了这个要点。
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
,是因为对于任意代码[1] [2] [3],没有用于终止Thread
或Task
的防弹方法。 根据您的需求,AppDomain似乎是对我最好的方法。
以下是超时的异步实现:
...
private readonly semaphore = new SemaphoreSlim(1,1);
...
// total time allowed here is 100ms
var tokenSource = new CancellationTokenSource(100);
try{
await WorkMethod(parameters, tokenSource.Token); // work
} catch (OperationCancelledException ocx){
// gracefully handle cancellations:
label.Text = "Operation timed out";
}
...
public async Task WorkMethod(object prm, CancellationToken ct){
try{
await sem.WaitAsync(ct); // equivalent to lock(object){...}
// synchronized work,
// call tokenSource.Token.ThrowIfCancellationRequested() or
// check tokenSource.IsCancellationRequested in long-running blocks
// and pass ct to other tasks, such as async HTTP or stream operations
} finally {
sem.Release();
}
}
这并不是说我提供建议,但你可以通过tokenSource
而不是它的Token
到WorkMethod
并定期做tokenSource.CancelAfter(200)
添加,如果你一定有更多的时间你是不是在一个点,可以是死锁定(等待一个HTTP调用),但我认为这将是一个多线程的深奥方法。
相反,如果你需要处理IO多线程(比如文件压缩,下载等)并避免死锁,你的线程应该尽可能快(最小的IO),一个线程可以序列化资源(生产者),而其他线程处理队列完全可能。
我非常喜欢使用陈述的视觉想法。 但是 ,这不是一个可行的解决方案。 为什么? 那么,子线程(using语句中的object / thread / timer)不能破坏主线程并注入异常,从而导致它停止正在做的事情并跳到最近的try / catch。 这就是这一切的结果。 我越坐在这里工作,就越发现。
总之,这不能按照我想要的方式完成。
但是,我采取了Pieter的方法,并且破坏了我的代码。 它确实引入了一些可读性问题,但我试图用评论等来缓解它们。
public void MyMethod( ... )
{
...
// Placeholder for thread to kill if the action times out.
Thread threadToKill = null;
Action wrappedAction = () =>
{
// Take note of the action's thread. We may need to kill it later.
threadToKill = Thread.CurrentThread;
...
/* DO STUFF HERE */
...
};
// Now, execute the action. We'll deal with the action timeouts below.
IAsyncResult result = wrappedAction.BeginInvoke(null, null);
// Set the timeout to 10 minutes.
if (result.AsyncWaitHandle.WaitOne(10 * 60 * 1000))
{
// Everything was successful. Just clean up the invoke and get out.
wrappedAction.EndInvoke(result);
}
else
{
// We have timed out. We need to abort the thread!!
// Don't let it continue to try to do work. Something may be stuck.
threadToKill.Abort();
throw new TimeoutException("This code block timed out");
}
...
}
由于我在每个主要部分的三到四个位置执行此操作,因此读起来更加困难。 但是,它工作得很好。
链接地址: http://www.djcxy.com/p/89525.html上一篇: How to create a generic timeout object for various code blocks?