C# 试图用秒表包装一个函数

C# Trying to wrap a function with a stopwatch

我一直在尝试查看函数在我的代码中执行需要多长时间,以了解我可以优化的地方。现在我使用一个助手 class,它本质上是一个秒表,带有一条消息来检查这些。这样做的目的是我应该能够在助手中包装我想要的任何方法调用,并且我会得到它的持续时间。

public class StopwatcherData
{
    public long Time { get; set; }
    public string Message { get; set; }

    public StopwatcherData(long time, string message)
    {
        Time = time;
        Message = message;
    }
}

public class Stopwatcher
{
    public delegate void CompletedCallBack(string result);

    public static List<StopwatcherData> Data { get; set; }
    private static Stopwatch stopwatch { get; set;}

    public Stopwatcher()
    {
        Data = new List<StopwatcherData>();
        stopwatch = new Stopwatch();
        stopwatch.Start();
    }

    public static void Click(string message)
    {
        Data.Add(new StopwatcherData(stopwatch.ElapsedMilliseconds, message));
    }

    public static void Reset()
    {
        stopwatch.Reset();
        stopwatch.Start();
    }
}

现在要使用这个,我必须在我想要的功能之前调用重置,以便重新启动计时器,然后在它之后调用点击。

Stopwatcher.Reset()
MyFunction();
Stopwatcher.Click("MyFunction");

我已经阅读了一些有关委托和操作的内容,但我不确定如何将它们应用于这种情况。理想情况下,我会将函数作为 Stopwatcher 调用的一部分传递。

//End Goal:
Stopwatcher.Track(MyFunction(), "MyFunction Time");

欢迎任何帮助。

是的,您可以创建一个计时器函数来接受任何操作作为委托。试试这个区块:

public static long TimeAction(Action action)
{
    var timer = new Stopwatch();
    timer.Start();
    action();
    timer.Stop();
    return timer.ElapsedMilliseconds;
}

可以这样使用:

  var elapsedMilliseconds = TimeAction(() => MyFunc(param1, param2));

如果你的包装函数 returns 是一个值,这会有点尴尬,但你可以通过从闭包中分配一个变量来处理这个问题,如下所示:

  bool isSuccess ;
  var elapsedMilliseconds = TimeToAction(() => {
    isSuccess = MyFunc(param1, param2);
  });

这样分析你的应用程序并不是一个好主意,但如果你坚持,你至少可以做一些改进。

首先,不要重复使用 Stopwatch,只需在每次需要时创建新的即可。

其次,您需要处理两种情况 - 一种是委托您传递了 return 的值,另一种是它没有。

由于您的 Track 方法是静态的 - 使其线程安全是一种常见的做法。非线程安全的静态方法是非常糟糕的主意。为此,您可以将消息存储在线程安全的集合中,例如 ConcurrentBag,或者每次将项目添加到列表时都使用 lock

最后你可以得到这样的结果:

public class Stopwatcher {
    private static readonly ConcurrentBag<StopwatcherData> _data = new ConcurrentBag<StopwatcherData>();

    public static void Track(Action action, string message) {
        var w = Stopwatch.StartNew();
        try {
            action();
        }
        finally {
            w.Stop();
            _data.Add(new StopwatcherData(w.ElapsedMilliseconds, message));
        }
    }

    public static T Track<T>(Func<T> func, string message) {
        var w = Stopwatch.StartNew();
        try {
            return func();
        }
        finally {
            w.Stop();
            _data.Add(new StopwatcherData(w.ElapsedMilliseconds, message));
        }
    }
}

并像这样使用它:

Stopwatcher.Track(() => SomeAction(param1), "test");
bool result = Stopwatcher.Track(() => SomeFunc(param2), "test");

如果您要将其与异步委托(return TaskTask<T>)一起使用 - 您需要为这种情况再添加两个重载。

我前段时间也遇到过这个问题,一直担心当我将 Stopwatcher.Track(() => SomeFunc(), "test")(参见 Evk 的回答)改回 SomeFunc() 时会留下错误。所以我想办法在不改变它的情况下包装它!

我想到了一个用法,这肯定不是预期的目的。

public class OneTimeStopwatch : IDisposable
{
    private string _logPath = "C:\Temp\OneTimeStopwatch.log";
    private readonly string _itemname;
    private System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

    public OneTimeStopwatch(string itemname)
    {
        _itemname = itemname;
        sw.Start();
    }

    public void Dispose()
    {
        sw.Stop();
        System.IO.File.AppendAllText(_logPath, string.Format($"{_itemname}: {sw.ElapsedMilliseconds}ms{Environment.NewLine}"));
    }
}

这个方法很简单

using (new OneTimeStopwatch("test"))
{
    //some sensible code not to touch
    System.Threading.Thread.Sleep(1000);
}
//logfile with line "test: 1000ms"

我只需要删除 2 行(和自动格式)即可使其恢复正常。 另外,我可以在这里轻松地换行多行,如果不在其他方法中定义新函数,这是不可能的。

同样,对于几毫秒的术语,不建议这样做。