Несоответствие потоков Delphi xe6

Итак, я всегда сталкивался с ОСНОВНЫМИ головными болями при работе с потоками в delphi xe4-6, будь то из-за того, что потоки не выполняются, обработка исключений вызывает сбои приложения или просто метод завершения, который никогда не вызывается. Все обходные пути, которые мне поручили использовать, стали очень утомительными, поскольку проблемы все еще преследуют меня в XE6. Мой код обычно выглядел примерно так:

procedure TmLoginForm.LoginClick(Sender: TObject);
var
  l:TLoginThread;
begin
  SyncTimer.Enabled:=true;
  l:=TLoginThread.Create(true);
  l.username:=UsernameEdit.Text;
  l.password:=PasswordEdit.Text;
  l.FreeOnTerminate:=true;
  l.Start;
end;



procedure TLoginThread.Execute;
var
  Success     : Boolean;
  Error       : String;
begin
  inherited;
  Success := True;
  if login(USERNAME,PASSWORD) then
  begin
    // do another network call maybe to get dif data.
  end else
  begin
    Success := False;
    Error   := 'Login Failed. Check User/Pass combo.';
  end; 

  Synchronize(
  procedure
    if success = true then
    begin
      DifferentForm.Show;
    end else
    begin
      ShowMessage('Error: '+SLineBreak+Error);
    end;
    SyncTimer.Enabled := False;
  end);
end;

И тут я наткнулся на этот агрегат из проб в Delphi и с форумов:

unit AnonThread;

interface

uses
  System.Classes, System.SysUtils, System.Generics.Collections;

type
  EAnonymousThreadException = class(Exception);

  TAnonymousThread<T> = class(TThread)
  private
    class var
      CRunningThreads:TList<TThread>;
  private
    FThreadFunc: TFunc<T>;
    FOnErrorProc: TProc<Exception>;
    FOnFinishedProc: TProc<T>;
    FResult: T;
    FStartSuspended: Boolean;
  private
    procedure ThreadTerminate(Sender: TObject);
  protected
    procedure Execute; override;
  public
    constructor Create(AThreadFunc: TFunc<T>; AOnFinishedProc: TProc<T>;
      AOnErrorProc: TProc<Exception>; ACreateSuspended: Boolean = False;
      AFreeOnTerminate: Boolean = True);

    class constructor Create;
    class destructor Destroy;
 end;

implementation

{$IFDEF MACOS}
uses
{$IFDEF IOS}
  iOSapi.Foundation
{$ELSE}
  MacApi.Foundation
{$ENDIF IOS}
  ;
{$ENDIF MACOS}

{ TAnonymousThread }

class constructor TAnonymousThread<T>.Create;
begin
  inherited;
  CRunningThreads := TList<TThread>.Create;
end;

class destructor TAnonymousThread<T>.Destroy;
begin
  CRunningThreads.Free;
  inherited;
end;

constructor TAnonymousThread<T>.Create(AThreadFunc: TFunc<T>; AOnFinishedProc: TProc<T>;
  AOnErrorProc: TProc<Exception>; ACreateSuspended: Boolean = False; AFreeOnTerminate: Boolean = True);
begin
  FOnFinishedProc := AOnFinishedProc;
  FOnErrorProc := AOnErrorProc;
  FThreadFunc := AThreadFunc;
  OnTerminate := ThreadTerminate;
  FreeOnTerminate := AFreeOnTerminate;
  FStartSuspended := ACreateSuspended;

  //Store a reference to this thread instance so it will play nicely in an ARC
  //environment. Failure to do so can result in the TThread.Execute method
  //not executing. See http://qc.embarcadero.com/wc/qcmain.aspx?d=113580
  CRunningThreads.Add(Self);

  inherited Create(ACreateSuspended);
end;

procedure TAnonymousThread<T>.Execute;
{$IFDEF MACOS}
var
  lPool: NSAutoreleasePool;
{$ENDIF}
begin
{$IFDEF MACOS}
  //Need to create an autorelease pool, otherwise any autorelease objects
  //may leak.
  //See https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html#//apple_ref/doc/uid/20000047-CJBFBEDI
  lPool := TNSAutoreleasePool.Create;
  try
{$ENDIF}
    FResult := FThreadFunc;
{$IFDEF MACOS}
  finally
    lPool.drain;
  end;
{$ENDIF}
end;

procedure TAnonymousThread<T>.ThreadTerminate(Sender: TObject);
var
  lException: Exception;
begin
  try
    if Assigned(FatalException) and Assigned(FOnErrorProc) then
    begin
      if FatalException is Exception then
        lException := Exception(FatalException)
      else
        lException := EAnonymousThreadException.Create(FatalException.ClassName);
      FOnErrorProc(lException)
    end
    else if Assigned(FOnFinishedProc) then
      FOnFinishedProc(FResult);
  finally
    CRunningThreads.Remove(Self);
  end;
end;

end.

Почему этот анонимный поток выше работает безупречно в 100% случаев, а мой код иногда дает сбой? Например, я могу выполнить один и тот же поток 6 раз подряд, но затем, возможно, в 7-й (или первый, если на то пошло) раз это приведет к сбою приложения. При отладке никогда не возникает исключений, поэтому я понятия не имею, с чего начать устранение проблемы. Кроме того, почему мне нужен отдельный таймер, который вызывает «CheckSynchronize» для моего кода, чтобы происходили обновления графического интерфейса, но он не нужен, когда я использую модуль анонимного потока?

Может быть, кто-то может указать мне правильное направление, чтобы задать этот вопрос в другом месте, если здесь не место. Извините, я уже погружаюсь в документацию, пытаясь изо всех сил понять.

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

inherited;
    try
      SQL:= 'Some SQL string'; 
      if GetSQL(SQL,XMLData) then
        synchronize(
        procedure
        var
          i:Integer;
        begin
          try
            mTasksForm.TasksListView.BeginUpdate;
            if mTasksForm.TasksListView.Items.Count>0 then
              mTasksForm.TasksListView.Items.Clear;
            XMLDocument := TXMLDocument.Create(nil);
            XMLDocument.Active:=True;
            XMLDocument.Version:='1.0';
            XMLDocument.LoadFromXML(XMLData);
            XMLNode:=XMLDocument.DocumentElement.ChildNodes['Record'];
            i:=0;
            if XMLNode.ChildNodes['ID'].Text <>'' then
            while XMLNode <> nil do
            begin
              LItem := mTasksForm.TasksListView.Items.AddItem;

              with LItem do
              begin
                Text := XMLNode.ChildNodes['LOCATION'].Text;

                Detail := XMLNode.ChildNodes['DESC'].Text +
                            SLineBreak+
                            'Assigned To: '+XMLNode.ChildNodes['NAME'].Text

                tag := StrToInt(XMLNode.ChildNodes['ID'].Text);
                color := TRectangle.Create(nil);
                with color do
                begin
                  if XMLNode.ChildNodes['STATUS'].Text = STATUS_DONE then
                    fill.Color := TAlphaColors.Lime  
                  else if XMLNode.ChildNodes['STATUS'].Text = STATUS_OK then
                    fill.Color := TAlphaColors.Yellow
                  else
                    fill.Color := TAlphaColors.Crimson;
                  stroke.Color := fill.Color;
                  ButtonText := XMLNode.ChildNodes['STATUS'].Text;
                end;
                Bitmap := Color.MakeScreenshot;
              end;
              XMLNode:=XMLNode.NextSibling;
            end;
          finally
            mTasksForm.TasksListView.EndUpdate;
            for i := 0 to mTasksForm.TasksListView.Controls.Count-1 do
            begin
              if mTasksForm.TasksListView.Controls[I].ClassType = TSearchBox then
              begin
                SearchBox := TSearchBox(mTasksForm.TasksListView.Controls[I]);
                Break;
              end;
            end;
            SearchBox.Text:=' ';
            SearchBox.text := ''; //have in here because if the searchbox has text, when attempting to add items then app crashes
          end;
        end)
      else
        error := 'Please check internet connection.';
  finally
    synchronize(
    procedure
    begin
      if error <> '' then
        ShowMessage('Erorr: '+error);

      mTasksForm.Spinner.Visible:=false;
      mTasksForm.SyncTimer.Enabled:=false;
    end);
  end;
end;

вот метод GETSQL

function GetSQL(SQL:String;var XMLData:String):Boolean;
var
  PostResult,
  ReturnCode          : String;
  PostData            : TStringList;
  IdHTTP              : TIdHTTP;
  XMLDocument         : IXMLDocument;
  XMLNode             : IXMLNode;

  Test                : String;
begin
  Result:=False;
  XMLData:='';
  XMLDocument:=TXMLDocument.Create(nil);
  IdHTTP:=TIdHTTP.Create(nil);
  PostData:=TStringList.Create;
  PostData.Add('session='+SessionID);
  PostData.Add('database='+Encode(DATABASE,''));
  PostData.Add('sql='+Encode(SQL,''));
  IdHTTP.Request.ContentEncoding:='UTF-8';
  IdHTTP.Request.ContentType:='application/x-www-form-urlencoded';
  IdHTTP.ConnectTimeout:=100000;
  IdHTTP.ReadTimeout:=1000000;
  try
    PostResult:=IdHTTP.Post(SERVER_URL+GET_METHOD,PostData);
    XMLDocument.Active:=True;
    XMLDocument.Version:='1.0';
    test := Decode(PostResult,'');
    XMLDocument.LoadFromXML(Decode(PostResult,''));
    XMLNode:=XMLDocument.DocumentElement;
    try
      ReturnCode:=XMLNode.ChildNodes['status'].Text;
    except
      ReturnCode:='200';
    end;
    if ReturnCode='' then begin
      ReturnCode:='200';
    end;
    if ReturnCode='200' then begin
      Result:=True;
      XMLData:=Decode(PostResult,'');
    end;
  except
    on E: Exception do begin
      result:=false;
    end;
  end;
  PostData.Free;
  IdHTTP.Free;
end;

person ThisGuy    schedule 26.08.2014    source источник
comment
Этот вопрос почти готов, но нам, вероятно, нужно увидеть mcve для неисправного кода.   -  person David Heffernan    schedule 26.08.2014
comment
Итак, половина кода в этом вопросе в значительной степени не имеет значения. Работающий код работает, потому что в нем нет ошибок. Ваш код не работает, потому что в нем есть ошибки. Огромная вставка нерелевантного кода без ошибок не поможет. Что касается вашего кода, я не вижу никакой обработки исключений в вашем методе Execute (большой красный флаг!), мы не знаем, что делает метод login(), и, кажется, вы закомментировали остальную часть своего фактического кода. ... Предлагаю вам удалить фрагмент из образцов Emba, показать остальную часть вашего фактического кода, и тогда, возможно, этот вопрос начнет становиться ответным...   -  person J...    schedule 26.08.2014
comment
Почему вы вызываете наследуемый метод выполнения вашего потока?   -  person A Lombardo    schedule 26.08.2014
comment
Разница в том, что ваш код показывает форму из Execute (при вызове Synchronize), а другой код — нет. Если вы пытаетесь выполнить вход из потока до запуска вашего приложения, это совершенно не нужно. Есть гораздо лучшие способы сделать то, что вообще не требует отдельного потока. Потоки никогда не должны напрямую взаимодействовать с пользовательским интерфейсом и никогда не должны полностью прерывать его, отображая форму, требующую ввода. Если вашему потоку требуется ввод данных от пользователя, сначала получите его, а затем предоставьте потоку при его запуске.   -  person Ken White    schedule 26.08.2014
comment
@ Кен, я не уверен, как ты пришел к выводу о большей части того, что написал? Вы можете увидеть событие щелчка, когда я инициализирую поток, извлекая входные данные из данных формы. Я думаю, это должно означать, что приложение работает, форма входа уже отображается... Функция входа - это просто процедура, которую нужно сделать убедитесь, что имя пользователя/пароль в порядке в базе данных. Я использую простой почтовый вызов с независимыми компонентами. @J Я включил много кода из «неуместного» анонимного блока, потому что, откровенно говоря, есть два типа посетителей: ленивые люди и люди, которые хотят видеть чистые вопросы (такие как вы). Я решил, что лучше сохранить   -  person ThisGuy    schedule 27.08.2014
comment
«ленивые» люди бегло просматривают вопрос, потому что им не хочется находить анонимную единицу или образец. Я также делаю несколько ссылок на обработку исключений, поэтому прошу прощения, если мое предположение в вашем предположении, что я уже принимаю меры предосторожности, неверно... (если это имеет смысл?). Я буду работать над тем, чтобы сделать свой вопрос более ясным, но он по-прежнему остается очевидным вопросом для... ПОЧЕМУ: должен ли я использовать таймер синхронизации, который вызывает «checksynchronize» с моим кодом, а не с аноновым блоком?   -  person ThisGuy    schedule 27.08.2014
comment
Хорошо ^ Теперь я понял. Хм... Так голо со мной. Таким образом, в примере я больше передаю указатель на функцию, которая обрабатывает графический интерфейс, а не синхронизируется с обработкой графического интерфейса (как я делал/делаю в предоставленном мной коде). Но я думал, что весь смысл метода синхронизации заключается в том, чтобы разрешить обработку элементов управления FMX/VCL?   -  person ThisGuy    schedule 27.08.2014
comment
Извините, но разница по-прежнему очевидна, как я и говорил ранее. Образец кода не имеет прямого доступа к визуальным элементам управления, а ваш делает это. я ничего не заключал; Я просто говорю, что код, который вы написали, является логически ошибочным IMO. У вас никогда не должно возникать необходимости отображать форму из потока, будь то через синхронизацию или нет. Ваша функция входа в систему должна запускать поток и позволять ему выполняться до завершения, получать успех/неудачу входа в систему и передавать его обратно, а вызывающая сторона должна обрабатывать результат.   -  person Ken White    schedule 27.08.2014
comment
Все это, кажется, становится не по делу - теперь, кажется, есть две проблемы и два вопроса. Во-первых, это приложение вылетает без объяснения причин - это проблема №1. Не видя остальной код в методе Execute потока, невозможно поставить диагноз, почему это происходит. Вызов Form.Show в методе Synchronize некрасиво, но это не должно нарушать работу приложения. Теперь проблема № 2 заключается в том, что вы думаете, что вам нужно вызывать CheckSynchronize в потоке пользовательского интерфейса (опять же, в коде, который мы не видим) по неизвестным нам причинам.   -  person J...    schedule 27.08.2014
comment
В дополнение к необходимости видеть дополнительный код, мой остающийся вопрос: почему вы думаете, что вам нужно вызвать CheckSynchronize (вы этого не делаете) - что произойдет, если вы не вызовете этот метод, как это отличается от того, что вы ожидаете, и как его вызов что-то меняет? Опять же, нам нужно увидеть оскорбительный код — что вы делаете в методе Login? Что происходит, когда вы закомментировали код? Куда ты звонишь CheckSynchronize?   -  person J...    schedule 27.08.2014
comment
@j и @ken, я обновил ОП   -  person ThisGuy    schedule 27.08.2014
comment
Когда у меня нет checkSynchronize в таймере, тогда поток запускается как следует, но то, что находится в Synchronize (начало процедуры...), никогда ничего не делает   -  person ThisGuy    schedule 27.08.2014
comment
Этот комментарий заставляет меня задуматься.... // ...because if the searchbox has text, when attempting to add items then app crashes. Вам не кажется это любопытным? Это ошибка прямо там. Почему бы не исправить это, а не взломать? Какие обработчики прикреплены к этому окну поиска?   -  person J...    schedule 28.08.2014
comment
Также рекомендуем вам прочитать и обратить внимание на раздел OSX: docwiki .embarcadero.com/Libraries/XE6/en/ — вы не сказали нам, какая платформа, но если кроссплатформенная, это может иметь значение.   -  person J...    schedule 28.08.2014
comment
К окну поиска не подключены обработчики, и вот ссылка на кого-то другого, у кого такая же проблема stackoverflow.com/questions/25248261/   -  person ThisGuy    schedule 28.08.2014
comment
ответ может быть таким же, поскольку в Delphi потоки все еще не работают, но вопрос не тот.   -  person ThisGuy    schedule 02.09.2014
comment
Он отвечает по крайней мере на один из вопросов в вашем вопросе.   -  person J...    schedule 03.09.2014
comment
Synchronize использует событие синхронизации для синхронизации управления с основным потоком. При обработке сообщений, когда обрабатывается нулевое сообщение или во время обработки в режиме ожидания, приложение вызывает CheckSynchronized. Если вам необходимо вызвать CheckSynchronized самостоятельно в основном коде (возможно, в цикле или по таймеру), возможно, у вас есть что-то, что мешает вашему приложению получать WM_NULL (возможно, код пытается его обработать?)   -  person Jasper Schellingerhout    schedule 18.06.2015
comment
Используйте IXMLDocument и функции LoadXMLData, LoadXMLDocument и NewXMLDocument вместо TXMLDocument и конструктора без владельца.   -  person Alex Kalabukhov    schedule 26.04.2018