Проблема с потоками (TIdHTTP?).
Опубликовано 25.12.2011 г. 23:26
Что-то разработка клиента для Myshows.ru с использованием FireMonkey встала на месте, а камнем предкновения стали потоки. Я в общем то далеко не мастер их использования, однако не пойму в чем может быть причина появления ошибки. Постарался сократить максимально исходный код, до тех пор пока ошибки остаются, но причину так и не понял.
Суть проблемы такова. Имеется 3 модуля: 1. форма 2. Класс представлющий список сериалов и т.п. и реализующий вызовы к API сервиса. 3. Класс реализующий API сервиса, и класс потока для использования API. Первый модуль формы ничего по сути не делает. просто запускает работу, и сигнализирует о том, что потоки завершены:
procedure TForm2.FormCreate(Sender: TObject); begin FShows := TMyShows.Create(); FShows.OnShowsLoaded := ShowsLoaded; FShows.OnUnwatchedUpdate := UnwatchedUpdate; FShows.Load(); end; procedure TForm2.ShowsLoaded(sender: TObject); begin button1.Caption := 'shows loaded'; end; procedure TForm2.UnwatchedUpdate(sender: TObject); begin button2.Caption := 'unwatched episodes loaded'; end;В обработчике события создания формы создается экземпляр объекта TMyShows. Назначаются 2 обработчика событий - событие того, что список сериалов получен от сервиса, и событие - от сервиса получены непросмотренные эпизоды. Сами обработчики просто, как видно, меняют надписи на кнопках, говоря нам о том, что поток завершен. Поскольку данные получаются от web-сервиса, то работа по их получению была вынесена в дополнительные потоки, чтобы из за таймаутов программа не подвисала, как это делает старая версия, которая однопоточна. Старт потоков вызовов к API происходит в FShows.Load(). Модуль №2. Управление сериалами - TMyShows. Тут осталось всего три метода. Первый - Load, который запускает потоки вызовов API. Вторые два метод SetShows & SetUnwatched() эмулируют что новые данные загружены, и соответственно вызывают события главной формы (код подчищен):
procedure TMyShows.SetShows(); begin if assigned(FOnShowsLoaded) then FOnShowsLoaded(nil); end; procedure TMyShows.SetUnwatched(); begin if assigned(FOnUnwatchedUpdate) then FOnUnwatchedUpdate(nil); end;Теперь метод Load(). Он создает два потока - TApiThread. Поток имеет свойство DoneProc - анонимный метод, который вызывается (в главном потоке, используя Synchronize), при завершении работы потока:
procedure TMyShows.Load(); var at : TApiThread; begin at := TApiThread.Create(); at.NameThreadForDebugging('Shows thread'); at.DoneProc := procedure(data : pointer) begin SetShows(); end; at.Start(); at := TApiThread.Create(); at.NameThreadForDebugging('unwatched thread'); at.DoneProc := procedure(data : pointer) begin SetUnwatched(); end; at.Start(); end;Т.е допустим первый поток стартовал, получил нужные данные от сервиса, и потом в главном потоке выполнил DoneProc, которая в свою очередь вызывает SetShows, которая уже уведомляет форму, что надо бы что данные получены, обработаны, и можно обновить интерфейс. Третий модуль - доступ к API. Здесь находится два класса - первый TShowsApi, просто описывает(ал) все возможные вызовы к API. Для доступа к API он использует TIdHttp. Поскольку работа ведется в несколько потоков, то имеется общий TIdCookieManager, которые хранит куки сессии. Общим он является за счет того, что объявлен классовой переменной (после урезания кода, переменная эта нигде не инициализируется, и не используется, но если ее закомментировать, то ошибка исчезает). Конструктор создает рабочий экземпляр TIdHttp, который разрушается в деструкторе:
TShowsAPI = class(TObject) strict private class var FCookie : TIdCookieManager; FHttp : TIdHttp; public constructor Create(); destructor Destroy(); override; end; ............ constructor TShowsAPI.Create(); begin inherited Create(); FHttp := TIdHttp.Create(nil); end; destructor TShowsAPI.Destroy(); begin FHttp.Free(); inherited; end;Последняя составляющая - поток. В своем распоряжении поток имеет во-первых - экземпляр TShowsApi, и во-вторых метод DoneProc (TApiDoneProc), который надо вызвать по завершении:
TApiDoneProc = reference to procedure(data : pointer); TApiThread = class (TThread) strict private FApi : TShowsApi; FData : pointer; FDoneProc : TApiDoneProc; procedure Done(); public constructor Create(); procedure Execute(); override; property DoneProc : TApiDoneProc read FDoneProc write FDoneProc; property Api : TShowsApi read FApi; end;Создается поток спящим, в Execute создается экземпляр TShowsApi, потом якобы у нас была полезная работа по обращению к сервису, потом мы вызываем DoneProс. Последний анонимный метод обернут в TApiThread.Done, чтобы его было можно использовать в Synchronize:
constructor TApiThread.Create(); begin inherited Create(true); FreeOnTerminate := true; end; procedure TApiThread.Execute(); begin FApi := TShowsApi.Create(); //FData := FApi.getSomething() if assigned(FDoneProc) then begin Synchronize(Done); end; FApi.Free(); end; procedure TApiThread.Done(); begin FDoneProc(FData); end;Вроде как бы все нормально. Но при работе возникает ошибка - Invalid Pointer Operation. Судя по отладчику возникает она, когда завершается второй поток и в конце Execute уничтожает свой экземпляр TShowsApi (FApi.Free). В свою очередь внутри деструктора FApi исключение происходит при уничтожении экземпляра FHttp : TIdHttp. По отладчику ошибка возникает после вызова деструктора TIdCustomHTTP в _ClassDestroy (system.pas) -> Instance.FreeInstace() -> _FreeMem(self), где вызов MemoryManager.FreeMem возвращает ошибку. Как я уже говорил, если закомментировать неиспользуемый FCookie : TIdCookieManager (общая классовая переменная для использования между потоками) то ошибки нет. Впрочем, если закомментировать создание/разрушение FHttp : TIdHttp, то ошибки тоже нет. Третья ситуация, при которой исчезает ошибка - закомментировать вызов Synchronize(Done). Однако тут стоит отметить, что если закомментить только внутренность Done - вызов FOnDone(FData), то ошибка остается. Перерыл несколько страниц гугла, но что то не нашел ответа. Сейчас использую XE2, мб в этом причина? Завтра попробую на работе проверить, что получится, там 2010я. Вот что то и не понятно мне в чем причина такого поведения. К статье прилагаю исходник VCL проекта (пространства имен убраны, так что код должен работать в любой версии Delphi, которая знает об анонимных методах), который сию проблему иллюстрирует: исходный код проекта Буду благодарен за советы в решении проблемы (: Обновление решение проблемы оказалось тривиальным до невозможности. Дело, как это часто бывает, в самой обычной невнимательности. Если посмотреть на начало объявления класса TShowsApi -
TShowsAPI = class(TObject) strict private class var FCookie : TIdCookieManager; FHttp : TIdHttp;то видно что переменная FCookie объявлена как class var, т.е классовая переменная. Но вот тут то ошибка и находится - секция class не заканчивается на одной этой переменной, и FHttp тоже является классовой. Так что все что нужно сделать - завершить секцию class, которая в свою очередь оканчивается при следующих условиях:
- Начинается секция var или другая секция class var
- Встречается объявление метода или функции (в т.ч классового)
- встречается объявление свойства (в т.ч. классового)
- Встречается объявление конструктора или деструктора
- указатель области видмости private/protected/public/published
26.12.2011 в 16:37
Запустил, кликнул - в дебаггере видно, что два потока запускаются и успешно завершаются...ter, надо больше информации по ошибке, т.к. пока она невоспроизводима. У меня Delphi XE2 Architect Update 3. Версия Indy та, которая идет по дефолту с Delphi..по-моему 10.5.7
26.12.2011 в 16:09
странно вобще. у меня просто валетает с эксепшеном. Потоки то как бы отрабатывают, надписи меняются, а потом уже при уничтожении выпадет EInvalidPointer. он у тебя случаем в игнор не попал? :)
зы: на обоих тестовых машинах Win 7 x32,
если запускать вне среды, то ошибок не видать никаких.
26.12.2011 в 17:24
26.12.2011 в 19:50
ты в аське бываешь часто или не? :)
26.12.2011 в 19:39
01.08.2017 в 06:46
-Notify me when new comments are added- checkbox and now whenever a comment is added I get four emails with the exact same comment.
There has to be a means you are able to remove me from that service?
Thank you!
01.08.2017 в 08:08