3

我想创建一个内部消息系统,可以告诉我调用某些代码的持续时间。我在考虑易于使用,使 SystemMessage 类实现 IDisposable。

我会在 SystemMessage 的构造函数期间设置一个时间戳,如果调用了 Dispose,我可以计算出持续时间。

问题是我不想让对象 GC'ed。我希望它作为 MessageCollection 的一部分保留。

C# 中是否有另一个构造可以让我使用 Using 语句的可用性,而无需踩到 IDisposable 的预期功能。

Using (message = Collection.CreateNewMessage("FileDownlading"))
{
    // I wonder how long it is taking me to download this file in production?
    // Lets log it in a message and store for later pondering.
    WebClass.DownloadAFile("You Know This File Is Great.XML");
}
// we fell out of the using statement, message will figure out how long
// it actually took to run.
// This was clean and easy to implement, but so wrong?
4

12 回答 12

5

问题是我不想让对象 GC'ed。我希望它作为 MessageCollection 的一部分保留。

调用 Dispose 不会导致对象被 GC'ed - 当 GC 进行扫描并且没有引用它时会发生这种情况。如果您仍然通过 MessageCollection 引用该对象,它会一直存在。

Dispose 可以防止它被 Finalized,但是由于您没有使用 Dispose 来清理资源,因此您将没有 Finalizer 并且您不会在意。

所以,真正唯一的问题是让你的 cals 实现 IDisposable 的语义令人困惑,即使没有要处理的资源。

就个人而言,我不认为这是一个问题。如果消费者调用 Dispose,那么很好 - 他们得到时间记录。如果他们不这样做,那么他们就不会获得 itme 邮票,最糟糕的是他们会违反 FxCop。

然而,它有点不直观 - 所以如果这是供公众使用,我建议提供一个更容易发现的替代方案,例如:

// C# 3+ lambda syntax
Collection.CreateNewMessage("FileDownlading", () => {
    // I wonder how long it is taking me to download this file in production?    
    // Lets log it in a message and store for later pondering.    
    WebClass.DownloadAFile("You Know This File Is Great.XML");
});

// C# 2 anonymous delegate syntax
Collection.CreateNewMessage("FileDownlading", delegate() {
    // I wonder how long it is taking me to download this file in production?    
    // Lets log it in a message and store for later pondering.    
    WebClass.DownloadAFile("You Know This File Is Great.XML");
});

// Method
void CreateNewMessage(string name, Action action) {
   StopWatch sw = StopWatch.StartNew();
   try {
      action();
   } finally {
      Log("{0} took {1}ms", name, sw.ElapsedMilliseconds);
   }
}

它将运行并计时一个 Action 委托。

于 2009-05-01T18:47:52.023 回答
2

你在寻找类似于闭包的东西吗?

http://en.wikipedia.org/wiki/Closure_(computer_science)

你可以伪造一些东西......像这样......

private TimeSpan GetDuration(Action a)
        {
            var start = DateTime.Now;
            a.Invoke();
            var end = DateTime.Now;
            return end.Subtract(start);
        }

        public void something()
        {
            string message;
            var timeSpan = GetDuration(() => { message = "Hello"; } );
        }
于 2009-05-01T18:35:50.930 回答
1

我不认为 using 是你想要的。为什么不让构造函数记录时间,然后在调用 DownloadAFile 时记录时间增量?在您的示例中,如果有异常,它将记录异常的时间,就好像当时下载了文件一样。

如果您真的希望行为像您的示例一样,只需使用 try / finally 块,然后在 finally 中进行日志记录。using只是 try / finally 块和调用的语法糖Dispose

您的示例的等效代码如下所示:

try 
{
    var message = Collection.CreateNewMessage("FileDownlading"); 
    //...
    WebClass.DownloadAFile("You Know This File Is Great.XML");
}
finally 
{
    //You can change this to do logging instead.
    message.Dispose(); 
}
于 2009-05-01T18:13:57.003 回答
1

您可以在 DownloadAFile() 中使用 System.Diagnostics.Stopwatch 来在每次调用时进行计时。

或者,只需在对 DownloadAFile() 的调用周围添加秒表代码(取决于您希望它如何工作)。

在这种情况下使用 IDisposable 不是一个好主意。

于 2009-05-01T18:16:11.867 回答
1

我最近一直在看这个,也许 PostSharp 可以帮助你。它允许您使用属性装饰方法,该属性将在您的方法启动和停止时调用。

http://www.postsharp.org/

我不确定它会做你喜欢的事,但它值得研究,它有你渴望的“句法糖”!

克里斯

于 2009-05-01T22:30:24.250 回答
1

好吧,这是一个老歌,但似乎没有其他人发布我认为最好的答案(根据我对您的要求的理解):

using (MessageTimer timer = Collection.CreateNewTimedMessage("blabla"))
{
   // ...
}

重要的一点是,它CreateNewTimedMessage可能会作为副作用创建和存储一个半永久性Message对象,但它会返回一个临时计时对象(使用StopWatch,或某种类似机制),该对象无法在using块的范围内生存。(MessageTimer可以引用Message,但不能反过来。)

因此,处理MessageTimer对象可能会产生在某处记录最后时间的副作用,但对象本身不会存活或复活;这不是对using构造的滥用,因为您确实在处理一个对象。

(实际的实现MessageTimer可能与乔的回答类似。)

于 2011-04-06T07:37:51.970 回答
0

The 'using' statement actually compiles down to a Try/Finally that expects the object to implement IDisposable--"using" is just a shortcut provided by the language.

For your purposes, especially since you want to recycle the object, I would consider writing your own interface. When you call a method to start timing just grab the value of datetime.now; and again when you stop the timer. Then subtract starttime from stoptime and you'll have the duration.

Avoid using a true Timer object on each class instance. Timer's use a ThreadPool thread, which means that each message will consume at least one thread--if too many messages are in the system your application will slow down as a result of the thread switching. Also, if the timer's aren't properly disposed they may not release their ThreadPool threads and you will essentially have a thread leak.

于 2009-05-01T19:02:34.810 回答
0

并不真地。你能来的最接近的是这样的(这就是 using() 语句的底层发生的事情:)

var message = Collection.CreateNewMessage("FileDownloading")

try
{
    WebClass.DownloadAFile("You Know This File Is Great.XML");
}
finally
{
    message.HowLongHaveIBeenAlive();
}
于 2009-05-01T18:16:13.830 回答
0

using 语句旨在在您完成对象后处理它们。在一个对象被丢弃后抓住它的想法并不完全是有意的。你可以尝试做一些简单的事情:

message = Collection.CreateNewMessage("FileDownlading");
DateTime dtStart = DateTime.Now;
WebClass.DownloadAFile("You Know This File Is Great.XML");
DateTime dtEnd = DateTime.Now;

// perform comparison here to see how long it took.

// dispose of DateTimes
dtStart = dtEnd = null;
于 2009-05-01T18:16:20.400 回答
0

After reviewing your question again I don't see how the object in question is really a Message, unless it is more like a tracing message (for debugging assistance).

If you're looking for something more along those lines then here is a very rough approach using delegates. Essentially you create a delegate for each method you want to call and time, then pass the delegate and the method arguments off to a helper who is responsible for the actual calling of the method and timing its duration.

The obvious shortcoming in my example is that I sacrificed type-safe arguments since I wasn't sure if it was exactly what you're looking for or not. The other issue is that you would need to add a new delegate every time you want to call a method which has a method signature that you don't already have a delegate for:

using System;

namespace ConsoleApplication4
{
    class Program
    {
        static void Main(string[] args)
        {

            SomeCaller callerOne;
            YetAnotherCaller callerTwo;

            callerOne = new SomeCaller(SomeMethod);
            LogCallDuration(callerOne, new object[] { 15 });

            callerOne = new SomeCaller(SomeOtherMethod);
            LogCallDuration(callerOne, new object[] { 22 });

            callerTwo = new YetAnotherCaller(YetAnotherMethod);
            LogCallDuration(callerTwo, null);

            Console.ReadKey();
        }

        #region "Supporting Methods/Delegates"

        delegate void SomeCaller(int someArg);
        delegate void YetAnotherCaller();

        static void LogCallDuration(Delegate targetMethod, object[] args)
        {
            DateTime start = DateTime.UtcNow;
            targetMethod.DynamicInvoke(args);
            DateTime stop = DateTime.UtcNow;

            TimeSpan duration = stop - start;

            Console.WriteLine(string.Format("Method '{0}' took {1}ms to complete", targetMethod.Method.Name, duration.Milliseconds));

        }

        #endregion "Supporting Methods/Delegates"

        #region "Target methods, these don't have to be in your code"
        static void SomeMethod(int someArg)
        {
            // Do something that takes a little time
            System.Threading.Thread.Sleep(1 + someArg);
        }

        static void SomeOtherMethod(int someArg)
        {
            // Do something that takes a little time
            System.Threading.Thread.Sleep(320 - someArg);
        }

        static void YetAnotherMethod()
        {
            // Do something that takes a little time
            System.Threading.Thread.Sleep(150);
        }
        #endregion "Target methods"
    }
}
于 2009-05-01T19:55:04.450 回答
0

最初IDisposable的目的是作为在 C# 中引入确定性清理的一种方式,但我已经看到作者实现使用using/Dispose功能来实现您所谈论的内容。

就个人而言,我对此不太满意,因为它破坏了背后的概念IDisposable,但由于没有其他选择,它归结为你想成为多正统的问题。我当然可以理解您为什么要这样做,但是这样做会使您更难以解释IDisposable界面的目的和重要性。

于 2009-05-01T18:38:00.220 回答
0

可以说这是对 using 构造的滥用,因此我可能不会在以这种方式使用 IDisposable 的共享类库中实现公共类。

但是我已经看到了这种事情,并且认为如果它保留在您的应用程序内部是可以的。

问题是我不想让对象 GC'ed。我希望它作为 MessageCollection 的一部分保留。

我完全不明白这一点。IDisposable 与 GC 无关,如果将其作为 MessageCollection 的元素引用,您的消息将保持活动状态。

您的消息类可能类似于下面的示例。调用 Dispose 后,它仍然存在并且运行良好。大多数实现 IDisposable 的类在调用 Dispose 后不可用,因此如果在调用 Dispose 后访问成员,它们的实现将引发 ObjectDisposedException。但这绝不是强制性的。

class Message : IDisposable
{
    private Stopwatch _stopwatch = Stopwatch.StartNew();
    private long _elapsedTicks;
    private string _message;

    public Message(string message)
    {
        _message = message;
    }

    public void Dispose()
    {
       _elapsedTicks = _stopwatch.ElapsedTicks;
       ... anything else including logging the message ...
    }

    ...
}
于 2009-05-01T18:42:45.063 回答