Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > Оформление кода потока


Автор: Alix 1.10.2007, 15:40
Нужно соединить две программы по сети. Использую WinSock2, впрочем это не важно. 
В каждой жмем "Соединиться" и начинается процесс соединения (кто сервер, кто клиент выбирается checkbox'ом). Процесс состоит из нескольких шагов, например для сервера они такие:
Код
  Создаем сервер
  Ожидаем подключение (ждем)
  Прием/передача данных, которые идентифицируют клиент, версию и некоторые другие параметры (посылаются в 2 этапа).

Проблема в том, что этот процесс должен быть в любой момент отменен, т.е. он должен исполняться в отдельном потоке, чтобы срабатывали controls на форме. В то же время поток должен на каждой стадии выводить состояние соединения в форму, в случае же неудачи на любой из стадий он опять таки должен выполнять код, который должен быть синхронизирован. 

Получается что код метода Execute потока будет выглядеть примерно так:
Код

  statusText := 'Создаем сервер...';
  Synchronize(updateStatus);

  socket_server := server_start(server_port);
  if socket_server = INVALID_SOCKET then begin
    Synchronize(createServerFailed); 
  end;

  statusText := 'Ожидаем подключение...';
  Synchronize(updateStatus);

  socket_client := server_waitclient(socket_server);
  if socket_client = INVALID_SOCKET then begin
    Synchronize(waitClientFailed); 
    exit;
  end;

  ...

И мне придется создавать кучу методов аля чтотоТамFailed. А даже если и сделать один метод failed и устанавливать код выхода (failedCode := fcCreateServer), то это тоже кажется страшненьким решением.

Впрочем решения красивее я не вижу. Скажите, есть ли оно? Или, быть может, я неправильно использую потоки?

Автор: stab 1.10.2007, 15:59
Я в таких случаях делаю события у потока, т.е. все методы вроде updateStatus или чтотоТамFailed находятся внутри класса-потока, а наружу они выдают обычные события в стиле VCL, например: DownloadFailed(Sender: TDownloader; Reason: TFailReason). Не думаю что лучшее решение есть.

Автор: Coder 1.10.2007, 16:04
Цитата(Alix @  1.10.2007,  23:40 Найти цитируемый пост)
Проблема в том, что этот процесс должен быть в любой момент отменен,

Может проверять на Terminate?
Код

      if Terminated then
         exit;


Цитата(Alix @  1.10.2007,  23:40 Найти цитируемый пост)
в случае же неудачи на любой из стадий он опять таки должен выполнять код, который должен быть синхронизирован. 

у тебя же есть statusText. Пишешь туда состояние и вызываешь одну процедуру синхронизации updateStatus

Цитата(Alix @  1.10.2007,  23:40 Найти цитируемый пост)
 А даже если и сделать один метод failed и устанавливать код выхода (failedCode := fcCreateServer), то это тоже кажется страшненьким решением.

почему страшненьким? сделай элементарный обработчик этих выходов:
Код

case failed of
  fcCreateServer : ;
  fcClientTimeout : ;
....
end;


Цитата(Alix @  1.10.2007,  23:40 Найти цитируемый пост)
socket_client := server_waitclient(socket_server);

Внутри server_waitclient() используется, как я понимаю, функция recv(), т.е. блокируящая сокет, то тебе перед ней советую вызывать select() и отлавливать таймауты.

Автор: Alix 1.10.2007, 17:12
stab, можно чуть подробнее? Вызов типа 
Код
if Assigned(FOnStatusText) then
  FOnStatusText('message');
 ? А как же синхронизация?

Цитата
почему страшненьким? сделай элементарный обработчик этих выходов:

да, конечно, но я имел в виду что код функции execute будет как-то страшненько выглядеть

Цитата
Внутри server_waitclient() используется, как я понимаю, функция recv(), т.е. блокируящая сокет, то тебе перед ней советую вызывать select() и отлавливать таймауты.

нет, там используется accept:

Код
function server_waitclient(server : TSocket) : integer;
var
  client : TSockAddr;
  size   : integer;
begin
  size   := sizeof(client);
  result := accept(server, @client, @size);
end;

Автор: stab 1.10.2007, 18:30
Цитата(Alix @  1.10.2007,  21:12 Найти цитируемый пост)
А как же синхронизация?

всё так же, через Synchronize(DoOnStatusMessage), а в DoOnStatusMessage уже собственно сам вызов обработчика события с параметрами.

Автор: Alix 1.10.2007, 18:57
А в чем тогда коренное отличие от моего способа, кроме того, что вызывается какая-то функция на событие?

Автор: stab 2.10.2007, 05:33
1. В том, что для внешнего когда поток может выглядеть как обычный компонент, внешнему коду даже знать не обязательно, что там поток есть. Плюс, обработчки на некоторые события, если они не нужны в каком-то конкретном случае, можно не вешать. В общем, бОльшая гибкость и стандартность подхода.

2. Повышение уровня инкапсуляции, т.к. не требуется объявления в паблик секции свойст вроде StatusText или LastErrorCode, эти данные могут быть просто переданны в обработчик события. В общем, меньше публичных состояний системы - больше надёжности.

Если надёжность\реюсабельность\изменяемость кода не важна, эти два пункта - пустой звук. Всё зависит от контекста и объёма задача.

Автор: ALeXandrK 2.10.2007, 06:57
http://forum.vingrad.ru/topic-60076.html
Там есть все, чтобы удачно применять потоки smile 
Пример на твою ситуацию там тоже есть...

Автор: Felan 2.10.2007, 07:18
stab, прав. Только вот если так делать, то в события неудобно параметры передавать, т.к. в Synchronaze нельзя вызывать метод с параметрами.

Есть еще один вариант.
В поточном классе просто сделал синхронизированные свойства которые отражали бы его состояние и позволяли бы управлять этим состоянием. Типа LastStatus - последний статус, в котором находится компонент. Abort - на следующем цикле метода execute прервать операцию и т.п.

А на форме можно поставить обычный таймер, для того что бы просто отображать текущее состояние...

Автор: Alix 2.10.2007, 08:09
Цитата
не требуется объявления в паблик секции свойст вроде StatusText или LastErrorCode

этого и так не делается...

Цитата
Только вот если так делать, то в события неудобно параметры передавать, т.к. в Synchronaze нельзя вызывать метод с параметрами.

Именно это я и имел в виду, когда говорил, что его код от моего мало отличается.

Цитата
В поточном классе просто сделал синхронизированные свойства которые отражали бы его состояние и позволяли бы управлять этим состоянием.

Никакого управления состоянием быть не должно. Код в потоке выполняется последовательно и там нет циклов. 

ALeXandrK, про статью знаю, сам выдавал на нее ссылки, в данный момент читаю, хотя и медленно - времени нет. Не подскажете, где там описана моя ситуация?

Автор: stab 2.10.2007, 08:13
Цитата(Alix @  2.10.2007,  12:09 Найти цитируемый пост)
этого и так не делается...


а это тогда зачем statusText := 'Создаем сервер...' ?

Цитата(Alix @  2.10.2007,  12:09 Найти цитируемый пост)
Именно это я и имел в виду, когда говорил, что его код от моего мало отличается.


я как раз таки описал способ как можно передавать параметры в обработчик.

Автор: Alix 2.10.2007, 09:27
stab, ты написал:
Цитата
всё так же, через Synchronize(DoOnStatusMessage), а в DoOnStatusMessage уже собственно сам вызов обработчика события с параметрами.

Я понял это так:
Код

// в описании TMyThread:
  private
    statusText : string; // у меня сейчас так и есть

// в методе Execute
  ...
  statusText := 'Создаем сервер...';
  Synchronize(DoOnStatusMessage);
  ...

procedure TMyThread.DoOnStatusMessage;
begin
  if Assigned(FOnStatusMessage) then
    FOnStatusMessage(statusText);
end;

В моем же варианте все то же самое, только в DoOnStatusMessage находится не вызов обработчика, а непосредственно его тело.

Автор: Felan 2.10.2007, 09:45
Цитата(Alix @  2.10.2007,  10:09 Найти цитируемый пост)
Никакого управления состоянием быть не должно. Код в потоке выполняется последовательно и там нет циклов. 

Ну так и не делай его. Все остальное ровно то же самое.

Я не пойму чем тебе не нравятся приведенные варианты? Вариант 
stabа,  хорош когда у тебя все на событиях. Мой  - когда нужен постоянно следить за состоянием а индикация уже вторична.

Автор: Alix 2.10.2007, 09:53
Felan, просто твой вариант мне не подходит, а метод stab'a такой же как и у меня по сути, я просто не вижу разницы между его вариантом и тем, что я привел в первом посте. (кроме, конечно, того, что у него все основано на событиях).

Автор: stab 2.10.2007, 10:46
Цитата(Alix @  2.10.2007,  13:27 Найти цитируемый пост)
В моем же варианте все то же самое, только в DoOnStatusMessage находится не вызов обработчика, а непосредственно его тело. 


а, ну ок тогда всё. я подумал что тема про события не была понята smile

Автор: Felan 2.10.2007, 12:15
Alix, По сути ее и нет. smile

Ну можешь еще сообщения посылать. Но это изврат какой-то...

Короче, как придумаешь свой уникальный способ - отпишись. Интересно, чего ты там кардинально нового выдумаешь.

Автор: Melancholic 4.10.2007, 07:48
Тоже думал на эту тему. Приведу общую постановку проблемы в моём понимании:
  • Наша программа занимается вводом-выводом посредствам сокетов.
  • Помимо ввода-вывода она занимается какой-то полезной работой. 
  • В системах, которых ввод-вывод отнимает достаточно много процессорного времени, для того чтобы не грузить основной поток вопросами IO (InputOutput) мы запускаем дополнительную нить.
Это с точки зрения потоков. Теперь посмотрим на проблему со стороны объектов:
  • Есть сущность "Главная форма". Она отвечает за обработку данных, поступивших от IO.
  • Есть объект-поток, который и занимается вводом-выводом.
Отсюда следует что как минимум неэстетично грузить поток ввода-вывода задачами обработки, что и происходит в предложенных вариантах (обработчик события выполняется в дополнительном потоке). Это главное что меня смущает в предложенных вариантах, хотя на абслютную правоту не претендую и в ряде задач такой подход может быть вполне оправдан. 
Какие есть альтернативы? Итак, снова начну с общего описания (это решение я нашел вообще в статье про последовательный порт smile):
  • Мы рассматриваем вопрос взаимодействия потоков.
  • Взаимодействие можно организовать посылкой сообщений.
  • Можно воспользоваться тем, что потоки выполняются в одном адресном пространстве и для посылки сообщения изменить заранее оговоренную область памяти.
Здесь можно вспомнить о сообщениях windows и использовать их в качестве этой самой "области памяти". т. е. Ваш дополнительный поток просто посылает окну сообщения (виндовыми средствами), а окно их обрабатывает как угодно. 
Теперь к частностям:
  • Скажете: "Пример в студию". Отвечу - его у меня нет smile Это всё так... Мысли вслух.
  • Как избавить код функции потока от постоянных проверок? Очевидное решение - исключения. Чуть что, - raise exception, а в конце метода execute обработка этих исключений с посылкой сообщения окну.

Автор: Felan 4.10.2007, 08:25
Цитата(Melancholic @  4.10.2007,  09:48 Найти цитируемый пост)
Это с точки зрения потоков. Теперь посмотрим на проблему со стороны объектов:

    * Есть сущность "Главная форма". Она отвечает за обработку данных, поступивших от IO.
    * Есть объект-поток, который и занимается вводом-выводом.

Отсюда следует что как минимум неэстетично грузить поток ввода-вывода задачами обработки, что и происходит в предложенных вариантах (обработчик события выполняется в дополнительном потоке). Это главное что меня смущает в предложенных вариантах, хотя на абсолютную правоту не претендую и в ряде задач такой подход может быть вполне оправдан. 


А из этого следует, что ты либо не понял, про что я тебе говорил, либо даже не захотел вникнуть.
То, что я тебе предложил как раз и есть решение того, что ты тут написал (Ну или может быть тут ошибочка smile).
Т.к. весь ВВ у тебя будет в твоем дополнительном потоке! Обработку этого ВВ можно включить куда угодно. Хоть в твою форму, хоть в то же поток, хоть еще один поток для него завести.
А форма будет отображать результаты которые даже можно не мониторить а как раз использовать сообщения, что бы уведомить форму о том, что надо бы обновить результаты таких-то операций.

Но это все ИМХО после прочтения твоего ТЗ smile

Автор: Melancholic 4.10.2007, 09:29
Вы правы, Felan, наши варианты похожи. За той лишь разницей что в моём случае поток ВВ сигнализирует о наступлении события посылкой сообщения, а в вашем состояние объекта-потока периодически проверяется таймером формы. Какой из вариантов применять - дело вкуса. Кстати если задача IO нересурсоёмка, то можно вообще использовать асинхронный ВВ, основанный на сообщениях (если кто не в курсе, то винда сама может уведомлять форму о наступлении события на сокете посылкой сообщения - и не надо никаких дополнительных потоков). (см. Использование сокетов в Delphi. Часть вторая: сокеты Windows, Антон Григорьев, дата публикации 01-10-2004 14:52 или описание функции WSAAsyncSelect в MSDN).

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)