主要使用BeginInvoke方法和ManualResetEvent类来实现。

BeginInvoke使得函数在线程池上异步运行,运行完成后,调用回调函数。

ManualResetEvent用于同步阻塞。

设计思想如下:

当函数在线程池中的某一线程上异步的运行的时候,ManualResetEvent阻塞当前线程,等待若干时间。

在等候期间,如果异步函数运行完毕,会对ManualResetEvent设置一个信号,使得阻塞的线程得以继续运行下去。

如果等候超时了,则阻塞的线程也会取消阻塞,继续运行下去,但是不再理会回调的函数。(即使回调函数仍然被调用),事实上,BeginInvoke创建的线程都是后台线程,这种线程一但所有的前台线程都退出后(其中主线程就是一个前台线程),不管后台线程是否执行完毕,都会结束线程,并退出。因此如果阻塞的主线程完全运行完毕退出,那么异步运行的线程也会退出,无论是否运行完毕。

语句isGetSignal = manu.WaitOne(timeout);就是阻塞当前线程一段时间。

该语句阻塞期间,不会对isGetSignal赋值,直到阻塞取消后,才会返回一个值给isGetSignal。

当阻塞是因为收到信号而取消的,得到的值是true。

当阻塞是因为超时而取消的,得到的值是false。

整个流程如下:

C#中,函数运行超时的功能的实现_第1张图片

把这些代码逻辑封装成一个类。

这个类就接受一个委托和一个超时时间作为构造函数。

把这个委托和 ManualResetEvent .Set();语句写在一个方法体内,CombineActionAndManuset,因此CombineActionAndManuset的调用就是实现了方法运行完毕后,设置取消阻塞信号。

封装后的代码:

   
   
   
   
  1. public class FuncTimeout  
  2.    {  
  3.        ///   
  4.        /// 信号量  
  5.        ///   
  6.        public ManualResetEvent manu = new ManualResetEvent(false);  
  7.        ///   
  8.        /// 是否接受到信号  
  9.        ///   
  10.  
  11.        public bool isGetSignal;  
  12.        ///   
  13.        /// 设置超时时间  
  14.        ///   
  15.        public int timeout;  
  16.        ///   
  17.        /// 要调用的方法的一个委托  
  18.        ///   
  19.        public Action<int> FunctionNeedRun;  
  20.  
  21.        ///   
  22.        /// 构造函数,传入超时的时间以及运行的方法  
  23.        ///   
  24.        ///   
  25.        ///   
  26.        public FuncTimeout(Action<int> _action, int _timeout)  
  27.        {  
  28.            FunctionNeedRun = _action;  
  29.            timeout = _timeout;  
  30.        }  
  31.  
  32.        ///   
  33.        /// 回调函数  
  34.        ///   
  35.        ///   
  36.        public void MyAsyncCallback(IAsyncResult ar)  
  37.        {  
  38.            //isGetSignal为false,表示异步方法其实已经超出设置的时间,此时不再需要执行回调方法。  
  39.            if (isGetSignal == false)  
  40.            {  
  41.                Console.WriteLine("放弃执行回调函数");  
  42.                Thread.CurrentThread.Abort();  
  43.            }  
  44.            else 
  45.            {  
  46.                Console.WriteLine("调用回调函数");  
  47.            }  
  48.        }  
  49.  
  50.        ///   
  51.        /// 调用函数  
  52.        ///   
  53.        ///   
  54.        public void doAction(int param1)  
  55.        {  
  56.            Action<int> WhatTodo = CombineActionAndManuset;  
  57.            //通过BeginInvoke方法,在线程池上异步的执行方法。  
  58.            var r=WhatTodo.BeginInvoke(param1, MyAsyncCallback, null);  
  59.            //设置阻塞,如果上述的BeginInvoke方法在timeout之前运行完毕,则manu会收到信号。此时isGetSignal为true。  
  60.            //如果timeout时间内,还未收到信号,即异步方法还未运行完毕,则isGetSignal为false。  
  61.            isGetSignal = manu.WaitOne(timeout);  
  62.              
  63.            if (isGetSignal == true)  
  64.            {  
  65.                Console.WriteLine("函数运行完毕,收到设置信号,异步执行未超时");  
  66.            }  
  67.            else 
  68.            {  
  69.                Console.WriteLine("没有收到设置信号,异步执行超时");  
  70.            }  
  71.        }  
  72.  
  73.        ///   
  74.        /// 把要传进来的方法,和 manu.Set()的方法合并到一个方法体。  
  75.        /// action方法运行完毕后,设置信号量,以取消阻塞。  
  76.        ///   
  77.        ///   
  78.        public void CombineActionAndManuset(int num)  
  79.        {  
  80.            FunctionNeedRun(num);  
  81.            manu.Set();  
  82.        }  
  83.    }  

测试代码:

 

   
   
   
   
  1. class Program  
  2.     {  
  3.         static void Main(string[] args)  
  4.         {  
  5.             FuncTimeout ft = new FuncTimeout(dosth, 3000);  
  6.             ft.doAction(6);  
  7.         }  
  8.  
  9.        static void dosth(int num)  
  10.         {  
  11.             for (int i = 0; i < num; i++)  
  12.             {  
  13.                 Thread.Sleep(500);  
  14.                 Console.Write(i);  
  15.             }  
  16.         }  
  17.     } 

 

当超时时间设置为5s的时候,方法未超时

A44D75E977694E5EA44DF12EA53445C6

当超时时间设置为1s的时候,方法超时

66AA2245B38746C29D953684881533F1