Windows Management Instrumentation. Опрос свойств, методов и описателей. Разработка компонента WMIControl, страница 16

Проблемы взаимодействия между потоками

Большинство из вас запускает приложение в режиме отладки (F5), а не в режиме выполнения (Ctrl+F5). Этот способ усложняет процесс отладки многопоточных приложений. Так как сам отладчик работает в рамках своего собственного (другого) потока, то при запуске в режиме отладки не будут работать все операторы, которые пытаются воздействовать на элементы управления splash-формы. Попробую объяснить суть проблемы кратко.

Система не позволяет обращаться к элементам управления из другого потока (она таким образом защищает данные приложения). Обоснование необходимости защищать элементы управления выглядит так. Если позволить дергать элементы управления из множества потоков, то показаниям такого элемента нельзя доверять. Представьте себе ползунок, позицией которого пытаются управлять 100 потоков приложения. Поэтому существует непреложное правило: на элемент управления можно воздействовать в рамках только того потока, в котором он был создан. Иначе система вырабатывает исключение вида: InvalidOperationException, в котором сообщает о попытке cross-thread operation.

Для того, чтобы понять, как решить проблему отладки многопоточных приложений, надо прочесть довольно много справочной информации. Смотрите справку по методам Invoke (или паре методов BeginInvoke-EndInvoke). Еще лучше прочесть серию статей на эту тему на сайте www.CodeProject.com.

Для того, чтобы внести изменения (например, в ListBox) из другого потока, надо обратиться к методу Invoke, который существует во всех классах, производных от класса Control. Синтаксис таков.

form.listItem.Invoke(new Action<string>(AddItem), new object[] { item });

Первый параметр — это делегат (адрес функции AddItem). Логика работы метода Invoke такова: мы посылаем второму приложению (у нас их теперь два) сообщение о том, чтобы оно вызвало метод AddItem и передало ему массив параметров (см. object[]). Эти действия будут произведены в том потоке, где была создана FormSplash и ее элементы управления, а не в потоке отладчика. Наш код учитывает эти особенности и вместо прямого изменения параметров элемента управления:

box.Items.Add(" " + msg);

производит это действие косвенно, путем вызова своего метода в рамках нужного потока:

box.Invoke(new Action<string>(AddItem), new object[] { msg });

Открытые методы AddMsg и AddItem провоцируют вызов самих себя в рамках того потока, в котором были созданы списки ListBox. При повторном вызове эти методы осуществляют взаимодействие с элементами и такой прием обеспечивает возможность пошаговой отладки кода (проблема межпотокового взаимодействия устранена). Свойство InvokeRequired существует в классе Control, а, следовательно и во всех его потомках. Оно позволяет оптимизировать описываемый процесс, так как не всегда есть необходимость перевызова метода в другом потоке. В этом можно убедиться добавив оператор: MessageBox.Show(box.InvokeRequired.ToString());

Вызов метода Invoke равносилен вызову API-функции SendMessage. Вызов метода BeginInvoke равносилен вызову API-функции PostMessage. Обе эти функции добавляют сообщение в очередь событий приложения. При выполнении SendMessage поток замораживается до конца обработки события. При выполнении PostMessage поток продолжает обработку очередных событий (сообщение добавляется в конец очереди событий). Поэтому говорят о синхронном и асинхронном способе запуска других потоков. Метод Invoke создает синхронный поток, а пара BeginInvoke-EndInvoke — асинхронный.

Action<T> — это новый делегатный тип, называемый generic delegate. Они появились в .NET потому, что разработчикам надоело создавать делегатные типы для каждой новой сигнатуры функций обратного вызова. Action<T> — это универсальный шаблон для множества делегатных типов. Меняя параметры шаблона, мы подстраиваемся под сигнатуру нужной нам функции.

¨  Задав Action<string>, мы определили сигнатуру в виде void ЛюбоеИмяФункции(string). Заметьте, что методы AddItem и AddMsg имеют именно такую сигнатуру.