
Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004
.pdf
432 ЧАСТЬ III Мощные средства и методы отладки приложений .NET
чет. При проверке кода контроль того, что исключения полностью документиро ваны, — одна из моих «горячих клавиш», и я всегда стараюсь убедиться, что это так.
Раз я упомянул о проверке кода и исключениях, укажу еще три цели, к кото рым всегда стремлюсь. Первая: блоки Finally в любых методах или свойствах, от крывающие что то, что может быть истолковано как ресурс с описателем (handle based resource), что обеспечивает очистку этих элементов. Я также ищу любые блоки catch (Exception) {…} или catch {…} и убеждаюсь, что они выполняют инициацию. Наконец, я всегда перепроверяю, чтобы повторные инициации не содержали после себя параметра, как здесь:
try
{
// Что то выполняем.
}
catch (DivideByZeroException e )
{
// НЕ ДЕЛАЙТЕ ЭТОГО!! throw e ;
}
Повторно инициируя исключение, вы теряете информацию о его происхождении. Последнее, что хочется отметить об исключениях, касается оператора using в C#. Оператор using разворачивается в тот же IL код, что и блок try…finally, и вы зывает метод Dispose для единственного объекта, указанного в операторе using. При менение оператора using абсолютно оправданно, но я предпочитаю этого не де
лать, так как происходящее за кадром не вполне очевидно.
Резюме
Концепция исключений в .NET радикально отличается от исключений в Win32. Благодаря ExceptionMon, у вас теперь есть способ мониторинга исключений, а значит, теперь вы сможете применять их более эффективно. Советую поэкспери ментировать с ExceptionMon — вы удивитесь тому, что происходит в ваших при ложениях.
Волшебная сила ExceptionMon в невероятном Profiling API. Поскольку Profiling API позволяет видеть все интересное, что происходит внутри CLR, в ваших руках огромная сила для написания таких инструментов, о которых в прошлом можно было только мечтать. Как вы увидите дальше, Profiling API позволяет делать еще больше.
Надеюсь, я дал вам пищу для размышлений о применении и реализации ис ключений в ваших собственных проектах .NET. Но все же библией в изучении исключений остаются главы книг Джеффри Рихтера, и я рекомендую вам прочесть их. Ключ в том, чтобы продумывать и планировать использование исключений с самого начала. Теперь в нашем распоряжении есть этот прекрасный инструмент, но если мы будем использовать его неправильно, то можем спровоцировать про блемы по мере развития продукта.

Г Л А В А
11
Трассировка программы
В главе 10 я вкратце затронул описание возможностей Profiling API. В этой гла ве я расскажу про Profiling API подробнее и рассмотрю программу, которую мне всегда хотелось иметь в своем распоряжении. В главе 6 я упоминал очень полез ную команду wt (Watch and Trace — наблюдение и трассировка) консольного от ладчика управляемого кода CORDBG.EXE. Как вы помните, она позволяет увидеть поток вызовов методов, а значит, и поток выполнения всей программы. Команда wt обеспечивает фантастический способ обнаружения «узких мест», которые просто невозможно найти путем простого изучения исходного кода.
К сожалению, CORDBG — консольное приложение, что не способствует его пониманию. Кроме того, CORDBG работает медленно, так как использует для трас сировки пошаговый механизм отладки. Я хочу, чтобы трассировка была быстрой, а выводимая в результате информация — простой в использовании. Вот для это го и нужна моя любимая утилита FlowTrace. Она дает вам силу wt без всякого горь кого привкуса!
Сначала я покажу, насколько легко и эффективно устанавливать ловушки для вызовов методов при помощи Profiling API. Объяснив, как использовать програм му FlowTrace, я опишу некоторые вопросы ее реализации, чтобы ее работа стала понятнее. Наконец, функциональность FlowTrace легко расширить, поэтому в конце главы я поделюсь кое какими идеями, которые помогут вам сделать эту програм му еще полезнее.
Установка ловушек при помощи Profiling API
Одна из самых сложных проблем при написании реальной программы профи лирования для Microsoft Win32 заключалась в том, что установить ловушки для потока вызовов функций было почти невозможно без значительной помощи со стороны компилятора или без изменения двоичного файла на диске. Поэтому о

434 ЧАСТЬ III Мощные средства и методы отладки приложений .NET
получении правильных временных интервалов, связанных с элементами пользо вательских приложений, оставалось только мечтать. Теперь этот механизм уведом ления о вызовах функций встроен в Profiling API — еще одно доброе дело со сто роны Microsoft, заслуживающее искренней благодарности. Благодаря этому раз работчики инструментов могут сосредоточиться на решении важных проблем профилирования, не тратя длительного времени на создание инфраструктуры своих утилит.
Запрос уведомлений входа и выхода
Profiling API позволяет получать уведомления обо всех вызовах методов и обо всех возвратах из них. Ключи /Gh и /GH (включающие функции ловушки _penter и _pexit соответственно) неуправляемого компилятора C++ играют по сути ту же роль, что и Profiling API, однако Profiling API делает уведомления еще проще, предоставляя также FunctionID выполняемой функции.
Как и в случае всех остальных уведомлений, исполняющей среде сначала нуж но сообщить, что вы хотите получать уведомления входа и выхода; для этого надо установить в битовой маске при помощи операции ИЛИ флаг COR_PRF_MONITOR_ENTER LEAVE и передать маску методу ICorProfilerInfo::SetEventMask. Готов спорить, что, как только вы запросите уведомления входа и выхода, вам не понадобится изме нять их на протяжении всего существования процесса. И все же славные парни из Microsoft позволяют вам включать/отключать уведомления входа и выхода сколь ко душе угодно. Не забывайте про эту возможность, так как на ее основе можно создать очень интересные инструменты, измеряющие, например, только время обработки исключений.
После этого исполняющей среде нужно сообщить, какие функции ловушки вам хотелось бы вызывать; это делается при помощи метода ICorProfilerInfo::SetEnter LeaveFunctionHooks, который принимает три указателя на вызываемые функции ловушки: функцию входа, функцию выхода и функцию выхода типа tailcall. На значение первых двух функций очевидно, а вот третья нуждается в пояснении. В настоящей версии CLR функции tailcall никогда не вызываются. Вызов типа tailcall имеет место, когда кадр стека текущего метода удаляется до выполнения коман ды call. Иначе говоря, если при вызове метода кадр стека вызывающего метода уже не нужен, он очищается. В будущих версиях CLR компилятор будет поддерживать tailcall оптимизацию, тогда она вам и понадобится. Так как для большинства пользо вателей Profile API различия между функцией выхода типа tailcall и обычной фун кцией выхода не имеют значения, можете с чистой совестью применять обычную функцию выхода.
Реализация функций-ловушек
Особенность установки ловушек заключается в определении функций ловушек. Для обеспечения максимально высокой производительности Profiling API требу ет, чтобы они использовали соглашение вызова naked. По сути ваши функции встра иваются в код JIT компилятором, так что вы должны сами написать для них про лог и эпилог.
Объявления typedef для всех функций ловушек выглядят так:

ГЛАВА 11 Трассировка программы |
435 |
|
|
typedef void FunctionEnter ( FunctionID funcID ) ;
Из документации не совсем ясно (к счастью, это становится понятным при изу чении примеров профилирования), что в одном аспекте функции ловушки похожи на стандартные вызовы: они сами отвечают за извлечение параметра FunctionID из стека. В комментариях в файле CorProf.IDL, которому всегда нужно доверять больше, чем Profiling.DOC, указано, что функции ловушки должны сохранять и все изменяемые регистры, в том числе регистры для работы с числами с плавающей точкой.
Пример функции ловушки — в листинге 11 1. Функции ловушки используют соглашение вызова naked, поэтому вы сами должны написать пролог и эпилог. Вся действительная работа выполняется в методе CFlowTrace::FuncEnter, таким образом, функция ловушка — всего лишь оболочка для его вызова. Пролог (первые три команды PUSH) сохраняет изменяемые регистры в стеке. Последние четыре коман ды — эпилог, который восстанавливает сохраненные регистры и выполняет воз врат из функции. Команда RET 4 возвращает и удаляет из стека переданный функ ции ловушке идентификатор функции, сохраняя мне одну команду POP.
Четыре команды, расположенные в середине функции, вызывают метод CFlow Trace::FuncEnter, передавая ему указатель на экземпляр класса и идентификатор функции. Идентификатор функции был передан функции ловушке входа. Теперь он находится в стеке на 16 (0x10) байт выше: до трех сохраненных регистров и адреса возврата. Команда PUSH [ESP + 10] помещает в стек его копию для передачи функции FlowTrace::FuncEnter. Внимательные читатели заметили, что в объявлении функции CFlowTrace::FuncEnter указано, что она принимает только один параметр. Это объясняется тем, что в методы классов C++ всегда сначала передается указатель на экземпляр класса (или указатель this); это скрытый параметр. Я пытался напи сать на встроенном ассемблере функцию ловушку меньшего объема, но мне кажется, что функцию, представленную в листинге 11 1, уменьшить уже невозможно.
Листинг 11-1. Пример функции-ловушки
void __declspec ( naked ) NakedEnter ( FunctionID /*funcID*/ )
{
__asm |
|
|
{ |
|
|
PUSH |
EAX |
// Сохранение изменяемых регистров. |
PUSH |
ECX |
|
PUSH |
EDX |
|
PUSH |
[ESP + 10h] |
// В стек в качестве параметра |
|
|
// помещается идентификатор функции. |
MOV |
ECX , g_pFlowTrace |
// В стек помещается указатель |
PUSH |
ECX |
// на экземпляр класса. |
CALL |
CFlowTrace::FuncEnter |
// Вызов метода FuncEnter. |
POP |
EDX |
// Восстановление сохраненных |
|
|
// регистров. |
POP |
ECX |
|
POP |
EAX |
|
см. след. стр.

436 |
ЧАСТЬ III |
Мощные средства и методы отладки приложений .NET |
||
|
|
|
|
|
|
|
|
|
|
|
|
RET 4 |
// Возврат и удаление из стека |
|
|
|
|
// полученного идентификатора |
|
|
|
|
// функции. |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
Встраивание
При обсуждении уведомлений от функций ловушек нельзя не рассмотреть вопрос встраивания (inlining). Ядро исполняющей подсистемы CLR оптимизировано, поэтому, чтобы сэкономить пару тактов процессора, оно очень часто будет встра ивать методы прямо в код. Это значит, что вы увидите сообщения о вызовах и возвратах не для всех методов, а только для тех, которые не были встроены.
Если вы хотите получить полный список всех вызовов программы, есть два способа отключения встраивания. Однако, как вы можете представить, запреще ние встраивания может заметно ухудшить быстроту выполнения управляемого кода. Проще всего отключить встраивание — установив при помощи операции ИЛИ флаг
OR_PRF_DISABLE_INLINING в битовой маске, передаваемой методу ICorProfilerInfo::Set EventMask при обработке уведомления ICorProfilerCallback::Initialize. Недостаток этого метода в том, что флаг COR_PRF_DISABLE_INLINING нельзя изменить, поэтому вы выключите его на все время жизни процесса независимо от того, где выполняет ся ваш код.
Второй способ предоставляет более точный контроль над встраиванием, но требует гораздо больше работы. В число получаемых вами уведомлений JIT вхо дит уведомление JITInlining, которое, как можно догадаться по его названию, ука зывает, что функция встраивается в другую функцию (для получения уведомле ний JIT нужно операцией ИЛИ установить в маске событий (event mask) флаг
COR_PRF_MONITOR_JIT_COMPILATION). JITInlining имеет такие параметры: FunctionID
вызывающей функции, FunctionID вызываемой (встраиваемой функции) и указа тель на BOOL, который при установке в FALSE предотвращает встраивание.
Уведомление JITInlining позволяет сделать очень интересные вещи. Например, вы можете оставить встраивание включенным для классов библиотеки классов .NET Framework (.NET Framework class library, FCL), отключив его для другого кода. Однако при этом нужно быть внимательным, так как CLR вызывает JITInlining огромное количество раз и, если ваш код каждый раз будет просматривать значения Func tionID вызывающей и вызываемой функции, это приведет к куда более серьезно му снижению быстродействия, чем выключение встраивания для всего процесса. Вы можете рассмотреть вариант сохранения интересующих вас значений FunctionID, но помните, что из за сборки мусора CLR они могут измениться, поэтому для поддержания таблиц данных в правильном состоянии вам придется обрабатывать уведомления сборки мусора.
Преобразователь идентификаторов функций
В дополнение к очень полезным функциям ловушкам мне нужно рассказать вам про еще одну специальную функцию — FunctionIDMapper. Ее предназначение зак лючается в изменении значений FunctionID, передаваемых трем названным выше
ГЛАВА 11 Трассировка программы |
437 |
|
|
функциям ловушкам. CLR вызывает ее перед любой из функций ловушек. Вы не обязаны применять FunctionIDMapper, однако это может открыть перед вами очень интересные возможности.
Изменение значений FunctionID при помощи FunctionIDMapper можно выполнить только один раз; это следует делать в методе ICorProfilerCallback::Initialize пу тем передачи указателя на функцию методу ICorProfilerInfo::SetFunctionIDMapper. В свое время у меня возникли проблемы из за того, что прототип этой функции в файле Profiling.DOC описан неверно. FunctionIDMapper возвращает тип UINT_PTR, соответствующий FunctionID, а не указанный в документе void. Вот правильный ее прототип:
UINT_PTR __stdcall FunctionIDMapper ( FunctionID functionId ,
BOOL *pbHookFunction ) ;
Интересно, что FunctionIDMapper использует стандартное соглашение вызова, а не соглашение naked, как функции, требуемые другими функциями ловушками. Параметр FunctionID — это функция, для которой CLR вызывает одну из функций ловушек. Указатель на Boolean позволяет указать CLR, следует ли ей на самом деле вызывать функцию ловушку. Если вы хотите разрешить вызов ловушки, присвой те *pbHookFunction значение TRUE. Если вы установите его в FALSE, функция ловуш ка вызываться не будет. Чтобы изменить значение, передаваемое в качестве пара метра функции ловушке, нужно возвратить это значение из FunctionIDMapper.
Мне кажется, что FunctionIDMapper будет интересной прежде всего тому, кто ра ботает с Profiling API в рамках крупных проектов. Так, почти при всех вызовах фун кций ловушек вы должны просматривать имена функций и методов. Вместо это го можно задействовать FunctionIDMapper, которая будет просматривать функции и передавать нужные значения функции ловушке. При этом просмотр функций будет выполняться в одном месте.
Благодаря контролю над действительными вызовами функций ловушек вы получаете в свое распоряжение еще больше возможностей. Так, если вам нужно протоколировать или анализировать выполнение только одного потока, то при помощи FunctionIDMapper вы можете определить идентификатор потока и, если он вас не интересует, пропустить функцию ловушку. Возможность пропуска функции ловушки облегчит реализацию программы профилирования. Я сам воспользовался этим преимуществом при написании программы FlowTrace.
Использование FlowTrace
Я ознакомил вас с основами установки ловушек при помощи Profiling API и те перь хочу перейти к описанию работы с FlowTrace. В результате этого вы лучше поймете некоторые вопросы реализации этой утилиты. Прежде всего хочу отме тить, что для настройки и запуска любых программ, работающих с Profiling API, нужно задать много переменных среды. Как и утилита ExceptionMon из главы 10, FlowTrace позволяет определить, хотите ли вы выполнить вызов DebugBreak в на чале программы (FLOWTRACEBREAK), а также указать, какой именно процесс вы же лаете профилировать (FLOWTRACEPROC). Кроме того, при помощи переменной сре ды FLOWTRACEFILEDIR можно указать конкретный каталог, в котором будут создаваться файлы вывода. Файл настройки FlowTrace, имеющий расширение .FLS, я опишу чуть

438 ЧАСТЬ III Мощные средства и методы отладки приложений .NET
ниже, а пока скажу, что, если вы зададите переменную FLOWTRACEFILEDIR, FlowTrace будет искать файл .FLS в указанном вами каталоге, а не там, где находится ваш исполняемый файл.
Чтобы облегчить работу с многопоточными приложениями, FlowTrace запи сывает сведения о потоках в разные файлы. Имена файлов формируются так:
<имя процесса>_<ID процесса Win32>_<ID управляемого потока>.FLOW
Отдельные части имени файла говорят сами за себя. Интерес представляет толь ко то, что вместо идентификатора потока Win32 я использовал идентификатор управляемого потока. Как вы увидите в разделе, посвященном реализации FlowTrace, взаимосвязь управляемых потоков и потоков Win32 отсутствует.
Наконец, перед использованием FlowTrace можно сконфигурировать необяза тельный файл параметров. Это простой файл инициализации, расположенный в том же каталоге, что и исполняемый файл, или в каталоге, указанном в перемен ной среды FLOWTRACEFILEDIR. Он называется так же, как и программа, только имеет расширение .FLS. Я мог бы задействовать для этого ультрасовременный файл XML, но мне не хотелось проводить несколько месяцев за написанием и тестировани ем кода C++, работающего с MSXML3.DLL, и раздувать рабочий набор FlowTrace десятками мегабайт. В усложнении вещей нет никакого смысла, если их можно сделать простыми.
Первый из трех необязательных параметров FlowTrace позволяет включить/ отключить встраивание. По умолчанию встраивание включено, благодаря чему все процессы, выполняемые под FlowTrace, работают быстрее. Второй параметр пред назначен для запрещения протоколирования потока финализации (finalizer thread). Все процессы .NET имеют сборщик мусора, работающий в отдельном потоке. Я обнаружил, что большинство вызовов потока финализации связано с очисткой объектов, созданных CLR. Чтобы минимизировать объем выводимой информации, я решил регистрировать только реальные действия процесса, выполняемые в других потоках, потому что мне кажется, что это дает более полезные сведения. Итак, по умолчанию FlowTrace не регистрирует поток финализации, но позволяет с лег костью включить эту функцию. Третий параметр определяет, протоколировать ли стартовый код для первоначальной AppDomain, создаваемой основным потоком, так как метод System.AppDomain.SetupDomain создает большой объем вывода. По умол чанию я не регистрирую стартовый код. В листинге 11 2 приведен файл .FLS по умолчанию. Чтобы включить конкретный параметр, присвойте ему 1; чтобы от ключить — 0.
Листинг 11-2. Файл .FLS по умолчанию
;Пример конфигурационного файла .FLS. Назовите этот файл
;так же, как и исполняемый, и поместите его в тот же каталог.
;Здесь указаны все общие параметры. Все они имеют
;значения по умолчанию, принимаемые, если исполняемый
;файл не имеет соответствующего файла .FLS.
[General Options]

ГЛАВА 11 Трассировка программы |
439 |
|
|
;Значение 1 отключает встраивание. Это приведет к получению
;гораздо большего объема информации о выполнении программы. TurnOffInlining=0
;Отключить обработку потока финализации. IgnoreFinalizerThread=1
;Пропускать все вызовы при создании AppDomain в основном потоке. SkipStartupCodeOnMainThread=1
Некоторые сведения о реализации FlowTrace
Теперь я хочу обсудить некоторые вопросы реализации FlowTrace. Первая про блема заключалась в том, что в будущем между управляемыми потоками и пото ками Win32 однозначного соответствия не будет. В первых версиях Microsoft .NET Framework такое соответствие имеется. Сначала я реализовал FlowTrace при по мощи надежного варианта локальной памяти потока, гарантирующего, что каж дый поток имеет специфические данные. Однако, изучая Profiling.DOC, я заметил специальное уведомление о потоке ICorProfilerCallback::ThreadAssignedToOSThread. В описании говорится: «Во время выполнения конкретный поток исполняющей среды может переключаться между различными потоками, что зависит от испол няющей среды и внешних компонентов, выполняющихся в процессе». Конечно, это не могло не привлечь моего внимания, и после консультации с программис тами Microsoft я понял, что простое решение с локальной памятью потока в буду щем работать не будет.
К счастью, уведомления интерфейса ICorProfilerCallback о создании и унич тожении потока предоставляют идентификатор управляемого потока в качестве параметра; кроме того, узнать идентификатор управляемого потока в любое вре мя позволяет ICorProfilerInfo::GetCurrentThreadID, так что идентификация управ ляемого потока проблем не представляет. Обратная сторона такого подхода зак лючалась в том, что мне нужно было создать собственную «локальную память уп равляемого потока» при помощи глобального класса отображения (map) библио теки стандартных шаблонов (STL). Конечно, для предотвращения проблем с не сколькими потоками я должен был сделать ее критической секцией. Многие ме тоды обратного вызова интерфейса IcorProfilerCallback в Profiling API очень не гативно относятся к блокировке при их обработке, поэтому я был немного смущен. Однако длительное тестирование позволяет мне утверждать, что это не оказывает заметного эффекта.
Вторая проблема была связана с тем, как пропускать стартовые вызовы, выпол няемые методом System.AppDomain.SetupDomain в основном потоке. Поэксперимен тировав с многочисленными управляемыми приложениями, я заметил, что в на чале приложение включает три потока. В документации к Profiling API упомина ется, что при внедрении программы профилирования в управляемый процесс для ее старта выделяется специальный поток, который, к счастью, не выполняет управ ляемого кода. Я обнаружил, что первое уведомление о создании потока всегда относилось к основному потоку приложения, а второе — к потоку финализации.

440 ЧАСТЬ III Мощные средства и методы отладки приложений .NET
Узнав, как идентифицировать потоки, я смог составить план пропуска стартово го кода. Для этого мне нужно было не регистрировать действий основного пото ка, пока функция ловушка выхода не увидит вызова System.AppDomain.ResetBinding Redirects.
Я мог видеть, какой поток выполняет функцию финализации, но я хотел так же, чтобы его можно было игнорировать. Сначала я хотел установить ловушку FunctionIDMapper, чтобы можно было проверять идентификатор управляемого по тока. Если бы этот идентификатор соответствовал потоку финализации, я присва ивал бы параметру pbHookFunction значение FALSE, чтобы CLR не вызывала функ цию ловушку. При тестировании этого способа на простейшей программе, напи санной на промежуточном языке, все работало великолепно.
Однако, тестируя FlowTrace с простым приложением Microsoft Windows Forms, я получил сообщения об ошибке, утверждавшие, что специфичные для управляе мого потока данные имеют значение NULL. Я игнорировал поток финализации, поэтому я не добавлял его в отображение управляемых потоков: я хотел, чтобы оно имело минимальный размер. Изучая эти ошибки, я заметил, что для потока финализации всегда вызывалась функция ловушка входа. Я решил, что допустил в алгоритме какую то ошибку, но в результате тщательной проверки так ничего и не обнаружил.
Зарегистрировав только эти специфические проблемы с потоком финализа ции и проштудировав документацию, я наконец выяснил, в чем дело. В приложе ниях Windows Forms все еще присутствуют некоторые странности COM, в том числе недостатки моделей разделенных потоков. То, что я видел, было кросс поточны ми вызовами с маршалингом из основного потока в поток финализации. Инте ресно, что CLR никогда не вызывала ловушку FunctionIDMapper. Значит, у меня не было способа заблокировать вызовы ловушек. Я надеялся, что мне не придется проверять поток финализации в функциях ловушках, чтобы не снижать быстро действия программы, но ничего не оставалось делать. Поэтому для избежания протоколирования вызовов финализации я должен был проверять поток фина лизации.
Все получилось, и я смог при необходимости отключать протоколирование финализации. Где то через день я понял, что вызывать FunctionIDMapper нужно, только когда пользователь специально запрашивает, чтобы я не отслеживал поток фина лизации. Первоначально я делал это во всех случаях.
Последняя проблема, с которой я должен был справиться, состояла в том, что бы гарантировать правильность выводимой информации в любых обстоятельствах. Это означало, что я должен был регистрировать для функций все «разворачивае мые» исключения, так как я никогда не встречал для них функцию ловушку вы хода. Задача оказалась достаточно простой. Для этого нужно было только вести для потока счетчик развертывания, когда CLR вызывала ICorProfilerCallback::Ex ceptionUnwindFunctionLeave. Как только исключение достигало ICorProfilerCallback::Ex ceptionCatcherLeave, я просто вычитал число развернутых функций для текущего уровня программы.
ГЛАВА 11 Трассировка программы |
441 |
|
|
Что после FlowTrace
FlowTrace — очень полезное средство обучения. Но, как и все утилиты, ее можно сделать еще лучше. Если вам нужен интересный проект, вот список некоторых отличных функций, которые вы могли бы попытаться добавить в FlowTrace.
Добавьте в файл .FLS функцию, позволяющую начинать и останавливать про токолирование для конкретных классов и методов. Благодаря этому вы смо жете точно указывать, что вы желаете регистрировать, не путаясь среди всех остальных сообщений. В идеале эта функция должна поддерживать протоко лирование как для отдельных, так и для всех потоков. Еще лучше реализовать указание интересующих вас классов и методов при помощи регулярных вы ражений; так вы сможете еще точнее определять, что нужно и не нужно реги стрировать.
Было бы неплохо начинать работу вообще без протоколирования и запускать его в конкретной точке при помощи внешнего события. Конечно, нужно реа лизовать и остановку протоколирования!
Вы можете добавить в FlowTrace возможность вывода времени выполнения функций. Вся необходимая для этого инфраструктура в FlowTrace уже реали зована.
Вместо сохранения вывода в текстовом файле вы могли бы записывать и ото бражать информацию в псевдореальном времени при помощи приложения с графическим интерфейсом.
Наконец, файлы вывода FlowTrace могут быть очень объемными. Неплохо было бы иметь возможность фильтрации частых базовых вызовов, таких как
Object..ctor.
Резюме
Определить последовательность вызовов по исходному коду иногда очень труд но. Надеюсь, FlowTrace сделает слежение за потоком выполнения ваших управля емых приложений более простой задачей, и вы сможете оптимизировать отладку и настройку производительности своих программ. Подумайте, какие другие ути литы можно разработать на основе Profiling API, настолько облегчающего уста новку ловушек для входа и выхода из функций. Конечно, у Profiling API есть и недостатки, но я все равно считаю, что это одно из самых удивительных средств платформы .NET.