生产中的C#代码计时

本文关键字:代码 生产中 | 更新日期: 2023-09-27 18:24:35

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

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

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记录器是一个好主意还是坏主意-我显然需要在某个时候查看数据,无论这意味着登录内存,然后直接转储或发送到文件。

生产中的C#代码计时

下面介绍如何进行一些依赖项注入来解决这个问题。

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

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接口有一个decorator。

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

因此,现在当调用doSomeWork.Execute()方法时,就会运行记录器代码,进而调用实际的执行代码。

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

现在,这个代码还不完美,因为我们有所有的SetFactorySetDecorator代码,它们会创建我们想要避免的依赖关系。

以下是我们如何绕过它。

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

则CCD_ 28和CCD_。它们看起来像这样:

[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类的程序集,将有效地删除日志记录。添加该程序集后,日志记录代码立即添加回来,无需重新编译。

简单。:-)