c# – 如何为各种代码块创建通用超时对象?

前端之家收集整理的这篇文章主要介绍了c# – 如何为各种代码块创建通用超时对象?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我有一系列代码块花了太长时间.它失败时我不需要任何技巧.事实上,我想在这些块花费太长时间时抛出异常,并且只是通过我们的标准错误处理而失败.我宁愿不为每个块创建方法(这是我到目前为止看到的唯一建议),因为它需要重写代码库.

如果可能的话,这就是我想要创建的内容.

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对我来说似乎是最好的方法.

猜你在找的C#相关文章