В Delphi, как известно, VCL (Visual Component Library) работает исключительно в главном потоке. Любые операции с VCL, будь то создание, изменение или уничтожение компонентов, должны выполняться в главном потоке. Нарушение этого правила может привести к непредсказуемым ошибкам, зависаниям и даже краху приложения.
Однако, на практике, особенно в сложных приложениях с многопоточностью, случайный доступ к VCL из сторонних потоков может произойти. Это может быть вызвано, например, перемещением обработчика события в другой поток, или вызовом события, на которое подписана форма VCL, из фонового потока.
Проблема: Как эффективно обнаружить такие несанкционированные обращения к VCL во время выполнения программы, чтобы своевременно выявить и исправить ошибки?
Решение Uwe Raabe и Remy Lebeau:
Начиная с Delphi 11 Alexandria, в VCL появилась возможность включить проверку на доступ к компонентам из сторонних потоков. Для этого необходимо установить свойство TControl.RaiseOnNonMainThreadUsage в True.
TControl.RaiseOnNonMainThreadUsage := True;
После установки этого свойства, при создании окна компонента (внутри CreateWnd), будет вызываться метод CheckNonMainThreadUsage, который, в случае обнаружения вызова из стороннего потока, вызовет исключение EInvalidOperation.
Важно отметить: Это свойство проверяется только при создании окна компонента. Remy Lebeau обратил внимание, что проверка не осуществляется при каждом обращении к компоненту. Поэтому, для более полного контроля, можно использовать метод TControl.CheckNonMainThreadUsage напрямую перед каждым потенциально опасным обращением к VCL.
procedure TMyThread.Execute;
begin
// ...
TThread.Synchronize(nil,
procedure
begin
if MyControl <> nil then
begin
MyControl.CheckNonMainThreadUsage; // Проверяем, что находимся в главном потоке
MyControl.Caption := 'Текст из потока'; // Обращаемся к VCL компоненту
end;
end);
// ...
end;
Альтернативное решение: Использование TVirtualMethodInterceptor
Der schöne Günther предложил интересный подход с использованием TVirtualMethodInterceptor. Идея заключается в перехвате вызовов ключевых методов VCL компонентов, таких как Repaint, Update, SetBounds, DefaultHandler, и вызове CheckNonMainThreadUsage перед их выполнением.
uses
System.Classes, Vcl.Controls, System.Rtti, System.TypInfo;
type
TNonMainThreadInterceptor = class(TVirtualMethodInterceptor)
private
procedure BeforeCall(Instance: TObject; Method: TRttiMethod; const Args: TArray<TValue>);
public
constructor Create(AClass: TClass); reintroduce;
end;
constructor TNonMainThreadInterceptor.Create(AClass: TClass);
begin
inherited Create(AClass);
// Перехватываем нужные методы
AddHook('Repaint', BeforeCall);
AddHook('Update', BeforeCall);
AddHook('SetBounds', BeforeCall);
AddHook('DefaultHandler', BeforeCall);
end;
procedure TNonMainThreadInterceptor.BeforeCall(Instance: TObject; Method: TRttiMethod; const Args: TArray<TValue>);
begin
if Instance is TControl then
begin
(Instance as TControl).CheckNonMainThreadUsage;
end;
end;
// Пример использования
procedure TForm1.FormCreate(Sender: TObject);
begin
TNonMainThreadInterceptor.Create(TForm); // Перехватываем методы всех форм
// Или можно перехватывать методы конкретных компонентов:
// TNonMainThreadInterceptor.Create(TButton);
end;
Преимущества и недостатки подхода с TVirtualMethodInterceptor:
Преимущества:
Автоматическая проверка перед вызовом ключевых методов.
Более гранулярный контроль, чем просто установка RaiseOnNonMainThreadUsage.
Недостатки:
Может повлиять на производительность, особенно если перехватывать методы слишком многих компонентов.
Требует более сложной настройки и понимания механизма перехвата виртуальных методов.
Необходимо тщательно выбирать перехватываемые методы, чтобы не пропустить важные случаи доступа к VCL из сторонних потоков.
Выбор подходящего решения:
Выбор подходящего решения зависит от конкретной ситуации и требований к приложению.
Если нужно быстро включить базовую проверку, достаточно установить TControl.RaiseOnNonMainThreadUsage := True.
Если требуется более точный контроль и отслеживание конкретных обращений к VCL, можно использовать TControl.CheckNonMainThreadUsage напрямую.
Для автоматизации процесса проверки и перехвата вызовов ключевых методов, можно использовать TVirtualMethodInterceptor, но следует учитывать возможные последствия для производительности.
Дополнительные рекомендации:
Тщательное проектирование многопоточного приложения: Старайтесь минимизировать взаимодействие между потоками и VCL. Используйте механизмы синхронизации, такие как Synchronize, для безопасного выполнения кода в главном потоке.
Четкая документация: Документируйте, какие части кода должны выполняться в главном потоке, а какие могут выполняться в сторонних потоках.
Регулярное тестирование: Тестируйте многопоточное приложение в различных условиях, чтобы выявить возможные проблемы с доступом к VCL из сторонних потоков.
В заключение, обнаружение несанкционированного доступа к VCL из сторонних потоков является важной задачей для обеспечения стабильности и надежности многопоточных приложений на Delphi. Использование предложенных решений, в сочетании с тщательным проектированием и тестированием, поможет избежать многих проблем и создать качественное программное обеспечение.
В Delphi, для обнаружения несанкционированного доступа к VCL из сторонних потоков, можно использовать свойство `TControl.RaiseOnNonMainThreadUsage`, метод `TControl.CheckNonMainThreadUsage` или технику перехвата виртуальных методов с помощью `TVirtualMet
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.