// Наш секундомер.
Stopwatch timer = new Stopwatch();
timer.Start(); // Запустим секундомер
try
{
Thread.Sleep(1234); // долгая операция
}
finally
{
timer.Stop(); // Остановим секундомер
// Показания секундомера:
TimeSpan elapsed = timer.Elapsed;
Console.WriteLine("Время выполнения {0}", elapsed);
}
В данном примере видны все неудобства использования Stopwatch, это необходимость явно создавать объект Stopwatch, это явный запуск и остановка секундомера, при этом, если возможны исключения, то нужно добавить и обработку исключений.
Решение данной проблемы очевидное, нужно применить интерфейс IDisposable в сочетании с блоком using. Для этого реализуем вспомогательный класс:
class Watcher : IDisposable
{
Action<TimeSpan> _action;
Stopwatch _timer;
// закрытый конструктор
Watcher(Action<TimeSpan> action)
{
_timer = new Stopwatch();
_action = action;
_timer.Start();
}
public void Dispose()
{
_timer.Stop();
if (_action != null)
{
try
{
_action(_timer.Elapsed);
}
catch
{
// не забываем, что Dispose
// безопасен относительно
// исключений
}
}
}
public static Watcher Start(Action<TimeSpan> action)
{
return new Watcher(action);
}
}
И пример использования:
using (Watcher.Start(t => Console.WriteLine(t)))
{
Thread.Sleep(1234); // долгая операция
}
Получилась достаточно удобная конструкция, допускающая вложенность любой глубины и разделяющая код на блоки:
using (Watcher.Start(t => Console.WriteLine(t)))
{
using (Watcher.Start(t => Console.WriteLine(t)))
{
// операция 1
}
using (Watcher.Start(t => Console.WriteLine(t)))
{
// операция 2
}
}
Класс Watcher не является законченным, его можно расширять на вкус и цвет. Например, если ведется лог, можно сбрасывать информацию сразу в лог, который можно получать при помощи IoC and DI pattern. Либо добавить header/footer информацию, которая будет записываться в лог при входе или выходе из блока.
Ссылки:
Что значит "Dispose безопасен относительно исключений"? Dispose не должен кидать исключения, если он вызывается несколько раз, но я впервые слышу, что Dispose должен игнорировать все исключения.
ОтветитьУдалитьСтоит избегать возникновения исключений в методе Dispose. Это хорошая практика и я стараюсь ее придерживаться.
ОтветитьУдалитьThe "Framework Design Guidelines" (2nd ed) has this as (§9.4.1):
"AVOID throwing an exception from within Dispose(bool) except under critical situations where the containing process has been corrupted (leaks, inconsistent shared state, etc.)"
http://stackoverflow.com/questions/577607/should-you-implement-idisposable-dispose-so-that-it-never-throws
спасибо, хорошая и простая идея!
ОтветитьУдалитьза 2 минуты прочитал и принял на вооружение
В Guidelines речь идет о том, должен ли Dispose сам кидать исключения. Полностью согласен, что не должен.
ОтветитьУдалитьНа stackoverflow обсуждается, должен ли Dispose игнорировать исключения, которые кидаются при освобождении дочерних ресурсов. Хоть это к нашему случаю и не относится, но правильный ответ - не должен. Если Dispose дочерних ресурсов следуют Guidelines, то они не будут кидать исключения, игнорировать нечего. Dispose(false) не должен освобождать управляемые ресурсы, значит опять игнорировать нечего.
В нашем случае в Dispose вызывается пользовательский код. Если этот код, например, протоколирует банковскую транзакцию, а протоколы потом используются для аудита, то игнорировать исключение нельзя.
Более общий вопрос - надо ли писать такой код:
try
{
// Some code
}
catch
{
try
{
// Buggy cleanup
}
catch
{
// Ignore exceptions
}
throw;
}
На мой взгляд нельзя, т.к. баг в cleanup-е никогда не будет исправлен. Сначала надо исправить все баги в catch/finaly блоках, потом уже искать баги в try.
В любом случае, игнорировать все исключения - очень плохо. Во-первых, нельзя игнорировать фатальные исключения (OutOfMemoryException, ...) Во-вторых, большая часть исключений являются следствием багов в коде, и эти баги надо исправлять.
PS. Если action == null, то зачем создавать и запускать таймер? Имеет ли смысл вызов Start(null)?
Спасибо!
ОтветитьУдалитьПростой и красивый способ!
Да, можно доработать Watcher, поставив проверку в конструкторе на action != null.
ОтветитьУдалитьЯ полностью согласен с Вами, что если мы протоколируем что-то очень важное, то ни в коем случае нельзя упускать никаких деталей, поэтому данный пример Watcher нужно доработать "под себя", добавив в catch дополнительную обработку,
а данный Watcher демонстрирует просто идею.
Ещё можно выставлять текущему потоку наивысший приоритет и возвращать предыдущее значение приоритета в Dispose()
ОтветитьУдалить