生产中的C#代码时序

时间:2014-09-24 23:37:14

标签: c# performance production stopwatch measurement

我希望在我的生产代码中实现可切换的代码计时工具,其想法是,当用户计算机上出现性能问题时,可以打开性能日志记录,以提供有用的数据来指示问题所在。

为此,我的逻辑是拥有一个实现bool属性TrackPerformance的工厂类和一个GetPerformanceTracker方法,该方法返回实现IPerformanceTracker接口的NullPerformanceTracker或LoggingPerformanceTracker。

Null显然没有做任何事情,另一个会写出Log4Net记录器,如果需要,可能会从正常记录中分离出来。我将使用StopWatch类进行计时。到目前为止一切都很好。

问题?

如果不过度影响性能本身,如何最好地实现这一点? 我正在考虑在MarkElapsedTime方法上使用编译器服务属性

MarkElapsedTime(string message, [CallerMemberName] callerMemberName = "", [CallerLineNumber] int = 0)

由于工厂调用的数量,在方法级别实例化计时器似乎是次优的。因此,似乎最好在类级别实例化它,在这种情况下,我需要将MarkElapsedTime调用绑定到相关的Start()调用,以便测量正确的经过时间量。 松散

class LoggingPerformanceTracker:IPerformanceTracker
{
    private readonly ILog mLogger = LogManager.GetLogger(....);

    private readonly StopWatch mStopWatch = new StopWatch();
    private readonly Dictionary<int, TimeSpan> mElapsed = new Dictionary<int, TimeSpan>();
    private int mNextId = 0;

    public void MarkElapsedTime(int timerId, string message, [CallerMemberName] callerMemberName = "", [CallerLineNumber] int = 0)
    {
        var ts = mStopWatch.Elapsed.Subtract(mElapsed[timerId]);

        if (mLogger.IsInfoEnabled)
            mLogger.Info(string.Format("{0}: {1} - [{2}({3})]", message, ts, callerMemberName, callerLineNumber));

    }

    public int Start()
    {
        if (!mStopWatch.IsRunning)
            mStopWatch.Start();

        var key = mNextId;
        mNextId++;
        mElapsed.Add(key, mStopWatch.Elapsed);

        return key;
    }
}

我之前没有必要这样做,并且考虑到这些测量调用将被放置在关键区域的代码库中,我希望第一次就能完成它。 使用Log4Net记录器也是一个好的或坏的想法 - 我显然需要在某些时候看到数据是否意味着登录内存然后直接转储或发送到文件。

1 个答案:

答案 0 :(得分:2)

所以这里是如何做一些依赖注入来解决这个问题。

首先,我们假设我们有这段代码:

public class DoSomeWork
{
    public void Execute()
    {
        Console.WriteLine("Starting");
        Thread.Sleep(500);
        Console.WriteLine("Done");
    }
}

这段代码执行一些(可能)长时间运行的任务。

我们可以这样称呼它:

    static void Main(string[] args)
    {
        var doSomeWork = new DoSomeWork();
        doSomeWork.Execute();
        Console.ReadLine();
    }

现在,要添加日志记录,我可以通过代码库并添加如下代码:

public class DoSomeWork
{
    public void Execute()
    {
        var sw = Stopwatch.StartNew();
        Console.WriteLine("Starting");
        Thread.Sleep(500);
        Console.WriteLine("Done");
        Console.WriteLine(sw.ElapsedMilliseconds);
    }
}

但这意味着,如果我想将日志记录代码添加到整个代码库中,我正在编辑大量文件并使我的代码更复杂。

有一种方法可以在不将记录代码添加到每个文件的情况下完成此工作。

首先,我们需要引入一个带有Execute方法的接口来抽象我们正在调用的代码。

public interface IDoSomeWork
{
    void Execute();
}

现在DoSomeWork类看起来像这样:

public class DoSomeWork : IDoSomeWork
{
    public void Execute()
    {
        Console.WriteLine("Starting");
        Thread.Sleep(500);
        Console.WriteLine("Done");
    }
}

现在调用代码如下所示:

    static void Main(string[] args)
    {
        var context = Context.CreateRoot();
        context.SetFactory<IDoSomeWork, DoSomeWork>();

        var doSomeWork = context.Resolve<IDoSomeWork>();
        doSomeWork.Execute();
        Console.ReadLine();
    }

现在,我使用了为此编写的依赖注入框架,但您可以使用Castle Windsor,Ninject等。

Context.CreateRoot()创建依赖注入容器。 context.SetFactory<IDoSomeWork, DoSomeWork>()配置容器,以便在我要求IDoSomeWork的实例实际返回DoSomeWork的实例时知道。

var doSomeWork = context.Resolve<IDoSomeWork>()要求容器尝试解析(创建或返回)实现IDoSomeWork的对象的实例。

从那里开始,代码就像原始代码一样运行。

现在我可以编写一个“装饰”具体类的日志类。

public class DoSomeWorkLogger : IDoSomeWork, IDecorator<IDoSomeWork>
{
    public void Execute()
    {
        var sw = Stopwatch.StartNew();
        this.Inner.Execute();
        Console.WriteLine(sw.ElapsedMilliseconds);
    }

    public IDoSomeWork Inner { get; set; }
}

此类实现IDoSomeWork以及我的容器所需的特殊接口IDecorator<IDoSomeWork>,以允许此类充当装饰器。

所以现在调用代码如下所示:

    static void Main(string[] args)
    {
        var context = Context.CreateRoot();
        context.SetFactory<IDoSomeWork, DoSomeWork>();
        context.SetDecorator<IDoSomeWork, DoSomeWorkLogger>();

        var doSomeWork = context.Resolve<IDoSomeWork>();
        doSomeWork.Execute();
        Console.ReadLine();
    }

context.SetDecorator<IDoSomeWork, DoSomeWorkLogger>()现在告诉容器有IDoSomeWork接口的装饰器。

所以当调用行var doSomeWork = context.Resolve<IDoSomeWork>()时,现在发生的是DoSomeWork的实例像以前一样创建,但是也创建了DoSomeWorkLogger的实例。 Inner实例的DoSomeWorkLogger属性是使用DoSomeWork的实例设置的,而DoSomeWorkLogger实例是从Resolve方法返回的。

所以现在当调用doSomeWork.Execute()方法时,运行记录器代码,然后调用实际的执行代码。

我无需更改DoSomeWork代码即可添加日志记录功能。

现在这段代码并不完美,因为我们拥有可以创建我们想要避免的依赖项的所有SetFactorySetDecorator代码。

所以这就是我们如何解决它。

首先,将IDoSomeWorkDoSomeWorkDoSomeWorkLogger代码移动到三个单独的程序集中。

然后DoSomeWorkDoSomeWorkLogger添加了两个特殊属性。它们看起来像这样:

[Factory(typeof(IDoSomeWork))]
public class DoSomeWork : IDoSomeWork { ... }

[Decorator(typeof(IDoSomeWork))]
public class DoSomeWorkLogger : IDoSomeWork, IDecorator<IDoSomeWork> { ... }

现在我可以将调用代码更改为:

    static void Main(string[] args)
    {
        var config = XDocument.Load(@"fileName");
        var context = Context.LoadRoot(config);

        var doSomeWork = context.Resolve<IDoSomeWork>();
        doSomeWork.Execute();
        Console.ReadLine();
    }

现在使用XML文件配置容器。 XML的格式并不重要,但是它可以在不重新编译代码的情况下进行更改。因此,通过将XML更改为不包含定义DoSomeWorkLogger类的程序集将有效地删除日志记录。添加该程序集,立即重新添加日志代码,无需重新编译。

简单。 : - )