Я недавно сыграл с одним демопроектом opensource для базовой функциональности сервера TCP/IP INDY10 и наткнулся на проблему внутренней многозадачной реализации INDY и ее взаимодействия с компонентами VCL. Поскольку в SO есть много разных тем, я решил сделать простое клиент-серверное приложение и проверить некоторые предлагаемые решения и подходы, по крайней мере те, которые я правильно понял. Ниже я хотел бы обобщить и рассмотреть подход, который ранее был предложен на SO, и, если возможно, выслушать свое мнение экспертов по этому вопросу.Класс обертки для потокобезопасных объектов
Проблема: инкапсуляция VCL для поточного использования внутри клиент-серверного приложения на основе indy10.
Описание окр развития .: Delphi Версия: Delphi® XE2 Версия 16,0 INDY Версия 10.5.8.0 младший матрос Windows 7 (32Bit)
Как упоминалось в статье ([Является ли VCL Thread-safe?]) (Извините, у меня недостаточно репутации, чтобы опубликовать ссылку) особое внимание следует уделить тому, когда вы хотите использовать любой вид компонентов VCL в многопоточном (многозадачном) приложении. VCL не является потокобезопасным, но может использоваться в потоковом режиме! Как и почему обычно зависит от приложения, но можно попытаться обобщить немного и предложить какой-то общий подход к этой проблеме. Прежде всего, как и в случае INDY10, не нужно явно распараллеливать его код, т. Е. Создавать и выполнять несколько потоков, чтобы подвергать VCL взаимоблокировкам и взаимозависимостям данных.
В каждом приложении sclient-server сервер должен иметь возможность обрабатывать несколько запросов одновременно, поэтому, естественно, INDY10 внутренне реализует эту функциональность. Это означало бы, что набор классов INDY10 отвечает за управление процессами создания, выполнения и уничтожения программы внутри.
Наиболее очевидным местом, где наш код подвергается внутренней работе INDY10 и, следовательно, возможных конфликтов потоков, является метод IdTCPServerExecute (TIdTCPServer onExecute).
Естественно, INDY10 предоставляет классы (обертки), которые обеспечивают поточный поток программ, но поскольку мне не удалось получить достаточное количество объяснений относительно их применения и использования, я предпочитаю индивидуальный подход.
Ниже я описываю метод (предложенный метод основан на предыдущем комментарии я нашел в SO How to use TIdThreadSafe class from Indy10), что попытки (и, предположительно, преуспевает) в решении этой проблемы:
Вопрос, который я решить ниже является : Как создать определенный класс «MyClass» ThreadSafe?
Основная идея состоит в том, чтобы создать класс оболочки, который инкапсулирует «MyClass» и ставит в очередь потоки, которые пытаются получить к нему доступ в принципе First-In-First-Out. Основными объектами, которые используются для синхронизации, являются [Объекты критической секции Windows.].
В контексте клиент-серверного приложения «MyClass» будет содержать все небезопасные функциональные возможности нашего сервера, поэтому мы постараемся гарантировать, что эти процедуры и функции не будут выполняться одновременно более чем одним рабочим потоком. Это, естественно, означает потерю параллелизма нашего кода, но поскольку подход прост и, по-видимому, в некоторых случаях, это может быть полезным подходом.
Упаковочный Класс реализации:
constructor TThreadSafeObject<T>.Create(originalObject: T);
begin
tsObject := originalObject; // pass it already instantiated instance of MyClass
tsCriticalSection:= TCriticalSection.Create; // Critical section Object
end;
destructor TThreadSafeObject<T>.Destroy();
begin
FreeAndNil(tsObject);
FreeAndNil(tsCriticalSection);
inherited Destroy;
end;
function TThreadSafeObject<T>.Lock(): T;
begin
tsCriticalSection.Enter;
result:=tsObject;
end;
procedure TThreadSafeObject<T>.Unlock();
begin
tsCriticalSection.Leave;
end;
procedure TThreadSafeObject<T>.FreeOwnership();
begin
FreeAndNil(tsObject);
FreeAndNil(tsCriticalSection);
end;
MyClass Определение:
MyClass = class
public
procedure drawRandomBitmap(abitmap: TBitmap); //Draw Random Lines on TCanvas
function decToBin(i: LongInt): String; //convert decimal number to Bin.
procedure addLineToMemo(aLine: String; MemoFld: TMemo); // output message to TMemo
function randomColor(): TColor;
end;
Использование:
Поскольку потоки выполняются в порядке и ждать поток, который имеет текущую собственность критической секции (tsCriticalSection.Enter и tsCriticalSection.Leave;) логично, что если вы хотите управлять этим правом собственности, вам нужен один уникальный экземпляр TThreadSafeObject (вы можете использовать одноэлементный шаблон). так включают в себя:
tsMyclass:= TThreadSafeObject<MyClass>.Create(MyClass.Create);
в Form.Create и
tsMyclass.Destroy;
в Form.Close; Здесь tsMyclass является глобальной переменной типа MyClass.
Использование:
Что касается использования MyClass попробовать следующее:
with tsMyclass.Lock do
try
addLineToMemo('MemoLine1', Memo1);
addLineToMemo('MemoLine2', Memo1);
addLineToMemo('MemoLine3', Memo1);
finally
// release ownership
tsMyclass.unlock;
end;
, где Memo1 является экземпляром компонента ТМето на форме.
С этим мы должны гарантировать, что все, что происходит, когда tsMyClass заблокировано будет выполняться только по одному потоку за раз. Однако очевидным недостатком этого подхода является то, что, поскольку у меня есть только один экземпляр tsMyclass, даже если один поток пытается рисовать, например. на холсте, в то время как другой пишет в Memo, первый поток должен будет дождаться завершения второго, и только тогда он сможет выполнить свою работу.
Мои вопросы здесь:
- ли выше предложенный метод правильно? Я все еще свободна от гонки условий или у меня есть некоторые «лазейки» в коде, откуда могут возникнуть конфликты данных ?
- Как можно, в общем, проверить нить небезопасность его применения?
Я хотел бы подчеркнуть, что вышеупомянутый подход никоим образом не является моим собственным. Это в основном резюме решения, найденного в 2. Тем не менее, я решил снова опубликовать сообщение, пытаясь получить какое-то закрытие по теме или какое-то доказательство действительности для предлагаемого решения. Кроме того, повторение - это мать всех знаний, как говорится.
Этот вопрос длинный, я даже не знаю с чего начать. –