可中断方法研究
最近两日在研究可中断方法的事,之前也写过一篇这样的文章。http://www.cnblogs.com/boyingwn/archive/2009/12/02/1615078.html
在网上搜到这个编程模型,核心代码如下:
原文:http://www.51testing.com/html/71/n-836371.html
public class FuncTimeout { /// <summary> /// 信号量 /// </summary> public ManualResetEvent manu = new ManualResetEvent(false); /// <summary> /// 是否接受到信号 /// </summary> public bool isGetSignal; /// <summary> /// 设置超时时间 /// </summary> public int timeout; /// <summary> /// 要调用的方法的一个委托 /// </summary> public Action<int> FunctionNeedRun; /// <summary> /// 构造函数,传入超时的时间以及运行的方法 /// </summary> /// <param name="_action"></param> /// <param name="_timeout"></param> public FuncTimeout(Action<int> _action, int _timeout) { FunctionNeedRun = _action; timeout = _timeout; } /// <summary> /// 回调函数 /// </summary> /// <param name="ar"></param> public void MyAsyncCallback(IAsyncResult ar) { //isGetSignal为false,表示异步方法其实已经超出设置的时间,此时不再需要执行回调方法。 if (isGetSignal == false) { Console.WriteLine("放弃执行回调函数"); Thread.CurrentThread.Abort(); } else { Console.WriteLine("调用回调函数"); } } /// <summary> /// 调用函数 /// </summary> /// <param name="param1"></param> public void doAction(int param1) { Action<int> WhatTodo = CombineActionAndManuset; //通过BeginInvoke方法,在线程池上异步的执行方法。 var r = WhatTodo.BeginInvoke(param1, MyAsyncCallback, null); //设置阻塞,如果上述的BeginInvoke方法在timeout之前运行完毕,则manu会收到信号。此时isGetSignal为true。 //如果timeout时间内,还未收到信号,即异步方法还未运行完毕,则isGetSignal为false。 isGetSignal = manu.WaitOne(timeout); if (isGetSignal == true) { Console.WriteLine("函数运行完毕,收到设置信号,异步执行未超时"); } else { Console.WriteLine("没有收到设置信号,异步执行超时"); } } /// <summary> /// 把要传进来的方法,和 manu.Set()的方法合并到一个方法体。 /// action方法运行完毕后,设置信号量,以取消阻塞。 /// </summary> /// <param name="num"></param> public void CombineActionAndManuset(int num) { FunctionNeedRun(num); manu.Set(); } }
还有这么一种编程模型的:
void CallWithTimeout(Action action, int timeoutMilliseconds) { Thread threadToKill = null; Action wrappedAction = () => { threadToKill = Thread.CurrentThread; action(); }; IAsyncResult result = wrappedAction.BeginInvoke(null, null); if (result.AsyncWaitHandle.WaitOne(timeoutMilliseconds)) { wrappedAction.EndInvoke(result); Console.WriteLine("正常执行结束。"); } else { Console.WriteLine("非正常执行结束。"); threadToKill.Abort(); } }
不过后面这个方法结束更为暴力一些,不过这两种方法都有一个共同的问题,就是超时资源的清理和返回值问题。
由于这个APM编程模型需要毫无调用EndInvoke函数,但是如果这个方法超时调用这个方法,会导致线程堵塞(这样就和我们的初衷相违背),不调用EndInvoke后果:如《CLR VIA C#》第三版 周靖译P690(27.8.2)。
必须调用EndXXX方法,否则会造成资源泄漏。首先在初始化异步操作时,CLR会分配一些内部资源。操作完成时,CLR会继续保留这些资源直至EndXXX方法被调用。如果不调用EndXXX方法,这些资源会一直分配,直至进程终止时才会被回收。其次,发起一个异步操作时,实际上并不知道该操作是成功失败。要知道这一点,只能通过调用EndXXX方法,检查它的返回值或看它是否抛出异常。
这个模型还面临和一个问题是如果我们想获取方法超时时的执行结果?如我们在进行Socket操作时一直期待终止符,但是我们想获取目前收到的字符串,目前使用的是这个方法来实现方法中断研究。
private string WaitFor(string prompt, out SendCmdStatus status, int timeOut, int times = 1, bool expliciteQuitNeEvent = true) { status = SendCmdStatus.Unknow; bool canExit = false; bool isTimeOut = false; string result = string.Empty; System.Threading.Timer timer = null; NetworkStream ns = client.GetStream(); Regex regex = new Regex(prompt + @"\s*.?\s*$", RegexOptions.IgnoreCase);//最后是否匹配到了结束符 while (!canExit) { try { if (client.Available > 0) { result += Read(ns, client.Available); var matches = regex.Matches(result); if (matches.Count == times) { canExit = true; status = SendCmdStatus.Success; break; } } } catch (Exception ex) { status = SendCmdStatus.Unknow; logger.Log(ClassName, "WaitFor", ex.ToString(), LogLevel.ERROR); } if (timer == null) { timer = new System.Threading.Timer(obj => { canExit = true; isTimeOut = true; }, canExit, timeOut, System.Threading.Timeout.Infinite); } } if (isTimeOut) { try { if (client.Available > 0) { result += Read(ns, client.Available); } } catch (Exception ex) { logger.Log(ClassName, "WaitFor", ex.ToString(), LogLevel.ERROR); } result += Read(ns, client.Available); if (expliciteQuitNeEvent) { ExplicitQuitNe(); } status = SendCmdStatus.Timeout; } if (timer != null) { timer.Dispose(); } return result; }
核心是使用System.Threading.Timer来实现这种操作。
理想的状态是可以有像Unix系统中的那种IO::Select来实现这种操作。
期待有更好的模型的高人指点一下(firetw@163.com),谢谢!
后来发现将数据保存到全局变量里,前面两种模型可以工作得很好。
UPD @20140312
后来又在书上的28.3.2章节发现,<<CLR via C#>>中使用的是timer方法实现的。建议以后使用这种方法。(可以在代码开始执行之前设置Timer)。
强制杀线程会使线程池内部重新生成线程,另外调用IAsyncResult的也有可能会导致线程池重新初始化新的线程,违背了线程池复用的初衷。另一种(不Kill线程)则会存在内存泄露的隐患,因为没有调用EndXxx.
public void work() { System.Threading.Timer timer=new System.Threading.Timer(TimeOutHandler,null,timeout,TimeOut.Infinite); //do something finally{ timer.Dispose();} }