Implement C# Generic Timeout

后端 未结 7 1935
谎友^
谎友^ 2020-11-22 09:39

I am looking for good ideas for implementing a generic way to have a single line (or anonymous delegate) of code execute with a timeout.

TemperamentalClass t         


        
7条回答
  •  陌清茗
    陌清茗 (楼主)
    2020-11-22 10:23

    We are using code like this heavily in production:

    var result = WaitFor.Run(1.Minutes(), () => service.GetSomeFragileResult());
    

    Implementation is open-sourced, works efficiently even in parallel computing scenarios and is available as a part of Lokad Shared Libraries

    /// 
    /// Helper class for invoking tasks with timeout. Overhead is 0,005 ms.
    /// 
    /// The type of the result.
    [Immutable]
    public sealed class WaitFor
    {
        readonly TimeSpan _timeout;
    
        /// 
        /// Initializes a new instance of the  class, 
        /// using the specified timeout for all operations.
        /// 
        /// The timeout.
        public WaitFor(TimeSpan timeout)
        {
            _timeout = timeout;
        }
    
        /// 
        /// Executes the spcified function within the current thread, aborting it
        /// if it does not complete within the specified timeout interval. 
        /// 
        /// The function.
        /// result of the function
        /// 
        /// The performance trick is that we do not interrupt the current
        /// running thread. Instead, we just create a watcher that will sleep
        /// until the originating thread terminates or until the timeout is
        /// elapsed.
        /// 
        /// if function is null
        /// if the function does not finish in time 
        public TResult Run(Func function)
        {
            if (function == null) throw new ArgumentNullException("function");
    
            var sync = new object();
            var isCompleted = false;
    
            WaitCallback watcher = obj =>
                {
                    var watchedThread = obj as Thread;
    
                    lock (sync)
                    {
                        if (!isCompleted)
                        {
                            Monitor.Wait(sync, _timeout);
                        }
                    }
                       // CAUTION: the call to Abort() can be blocking in rare situations
                        // http://msdn.microsoft.com/en-us/library/ty8d3wta.aspx
                        // Hence, it should not be called with the 'lock' as it could deadlock
                        // with the 'finally' block below.
    
                        if (!isCompleted)
                        {
                            watchedThread.Abort();
                        }
            };
    
            try
            {
                ThreadPool.QueueUserWorkItem(watcher, Thread.CurrentThread);
                return function();
            }
            catch (ThreadAbortException)
            {
                // This is our own exception.
                Thread.ResetAbort();
    
                throw new TimeoutException(string.Format("The operation has timed out after {0}.", _timeout));
            }
            finally
            {
                lock (sync)
                {
                    isCompleted = true;
                    Monitor.Pulse(sync);
                }
            }
        }
    
        /// 
        /// Executes the spcified function within the current thread, aborting it
        /// if it does not complete within the specified timeout interval.
        /// 
        /// The timeout.
        /// The function.
        /// result of the function
        /// 
        /// The performance trick is that we do not interrupt the current
        /// running thread. Instead, we just create a watcher that will sleep
        /// until the originating thread terminates or until the timeout is
        /// elapsed.
        /// 
        /// if function is null
        /// if the function does not finish in time 
        public static TResult Run(TimeSpan timeout, Func function)
        {
            return new WaitFor(timeout).Run(function);
        }
    }
    

    This code is still buggy, you can try with this small test program:

          static void Main(string[] args) {
    
             // Use a sb instead of Console.WriteLine() that is modifying how synchronous object are working
             var sb = new StringBuilder();
    
             for (var j = 1; j < 10; j++) // do the experiment 10 times to have chances to see the ThreadAbortException
             for (var ii = 8; ii < 15; ii++) {
                int i = ii;
                try {
    
                   Debug.WriteLine(i);
                   try {
                      WaitFor.Run(TimeSpan.FromMilliseconds(10), () => {
                         Thread.Sleep(i);
                         sb.Append("Processed " + i + "\r\n");
                         return i;
                      });
                   }
                   catch (TimeoutException) {
                      sb.Append("Time out for " + i + "\r\n");
                   }
    
                   Thread.Sleep(10);  // Here to wait until we get the abort procedure
                }
                catch (ThreadAbortException) {
                   Thread.ResetAbort();
                   sb.Append(" *** ThreadAbortException on " + i + " *** \r\n");
                }
             }
    
             Console.WriteLine(sb.ToString());
          }
       }
    

    There is a race condition. It is clearly possible that a ThreadAbortException gets raised after the method WaitFor.Run() is being called. I didn't find a reliable way to fix this, however with the same test I cannot repro any problem with the TheSoftwareJedi accepted answer.

提交回复
热议问题