Domanda

Mi sono appena reso conto che le mie eccezioni non vengono mostrate all'utente nei miei thread!

All'inizio l'ho usato nel mio thread per sollevare l'eccezione, che non funziona:

except on E:Exception do
begin
  raise Exception.Create('Error: ' + E.Message);
end;

L'IDE mi mostra le eccezioni, ma la mia app no!

Ho cercato una soluzione, questa è quella che ho trovato:

Meccanismo di eccezione del thread Delphi

http://www.experts-exchange.com/Programming/Languages/Pascal/Delphi/Q_22039681.html

E nessuno di questi ha funzionato per me.

Ecco la mia unità di discussione:

unit uCheckForUpdateThread;

interface

uses
  Windows, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
  IdHTTP, GlobalFuncs, Classes, HtmlExtractor, SysUtils, Forms;

type
  TUpdaterThread = class(TThread)
  private
    FileGrabber : THtmlExtractor;
    HTTP : TIdHttp;
    AppMajor,
    AppMinor,
    AppRelease : Integer;
    UpdateText : string;
    VersionStr : string;
    ExceptionText : string;
    FException: Exception;
    procedure DoHandleException;
    procedure SyncUpdateLbl;
    procedure SyncFinalize;
  public
    constructor Create;

  protected
    procedure HandleException; virtual;

    procedure Execute; override;
  end;

implementation

uses
  uMain;

{ TUpdaterThread }

constructor TUpdaterThread.Create;
begin
  inherited Create(False);
end;

procedure TUpdaterThread.Execute;
begin
  inherited;
  FreeOnTerminate := True;

  if Terminated then
    Exit;

  FileGrabber           := THtmlExtractor.Create;
  HTTP                  := TIdHTTP.Create(nil);
  try
    try
      FileGrabber.Grab('http://jeffijoe.com/xSky/Updates/CheckForUpdates.php');
    except on E: Exception do
    begin
      UpdateText := 'Error while updating xSky!';
      ExceptionText := 'Error: Cannot find remote file! Please restart xSky and try again! Also, make sure you are connected to the Internet, and that your Firewall is not blocking xSky!';
      HandleException;
    end;
    end;

    try
      AppMajor      := StrToInt(FileGrabber.ExtractValue('AppMajor[', ']'));
      AppMinor      := StrToInt(FileGrabber.ExtractValue('AppMinor[', ']'));
      AppRelease    := StrToInt(FileGrabber.ExtractValue('AppRelease[[', ']'));
    except on E:Exception do
    begin
      HandleException;
    end;
    end;

    if (APP_VER_MAJOR < AppMajor) or (APP_VER_MINOR < AppMinor) or (APP_VER_RELEASE < AppRelease) then
    begin
      VersionStr := Format('%d.%d.%d', [AppMajor, AppMinor, AppRelease]);
      UpdateText := 'Downloading Version ' + VersionStr;
      Synchronize(SyncUpdateLbl);
    end;

  finally
    FileGrabber.Free;
    HTTP.Free;
  end;
  Synchronize(SyncFinalize);
end;

procedure TUpdaterThread.SyncFinalize;
begin
  DoTransition(frmMain.TransSearcher3, frmMain.gbLogin, True, 500);
end;

procedure TUpdaterThread.SyncUpdateLbl;
begin
  frmMain.lblCheckingForUpdates.Caption := UpdateText;
end;

procedure TUpdaterThread.HandleException;
begin
  FException := Exception(ExceptObject);
  try
    Synchronize(DoHandleException);
  finally
    FException := nil;
  end;
end;

procedure TUpdaterThread.DoHandleException;
begin
  Application.ShowException(FException);
end;

end.

Se hai bisogno di maggiori informazioni fammi sapere.

Ancora:L'IDE rileva tutte le eccezioni, ma il mio programma non le mostra.

MODIFICARE:È stata la soluzione di Cosmin che ha funzionato alla fine - e il motivo per cui all'inizio non ha funzionato è perché non ho aggiunto la variabile ErrMsg, invece ho semplicemente inserito tutto ciò che la variabile avrebbe contenuto in Synchronize, che NON avrebbe funzionato, comunque Non ho idea del perché.Me ne sono reso conto quando non avevo altre idee, e ho solo pasticciato con le soluzioni.

Come sempre, lo scherzo è su di me.=P

È stato utile?

Soluzione

Ecco la mia "presa" molto breve sulla questione. Funziona solo su Delphi 2010+ (perché quella versione ha introdotto metodi anonimi). A differenza dei metodi più sofisticati già pubblicati, il mio mostra solo il messaggio di errore, niente di più, niente di meno.

procedure TErrThread.Execute;
var ErrMsg: string;
begin
  try
    raise Exception.Create('Demonstration purposes exception');
  except on E:Exception do
    begin
      ErrMsg := E.ClassName + ' with message ' + E.Message;
      // The following could be all written on a single line to be more copy-paste friendly  
      Synchronize(
        procedure
        begin
          ShowMessage(ErrMsg);
        end
      );
    end;
  end;
end;

Altri suggerimenti

Qualcosa di molto importante che devi capire sullo sviluppo multi-thread:

Ogni thread ha il proprio stack di chiamate, Quasi come se fossero programmi separati.Ciò include il thread principale del tuo programma.

I thread possono interagire tra loro solo in modi specifici:

  • Possono operare su dati o oggetti condivisi. Ciò può portare a "condizioni di gara" di problemi di concorrenza e pertanto è necessario essere in grado di aiutarli a "condividere bene i dati".Il che ci porta al punto successivo.
  • Possono "segnalarsi a vicenda" utilizzando una varietà di routine di supporto del sistema operativo.Questi includono cose come:
    • Mutex
    • Sezioni critiche
    • Eventi
  • E infine puoi inviare messaggi ad altri thread. A condizione che il thread sia stato in qualche modo scritto per essere un destinatario del messaggio.

NB:Tieni presente che i thread non possono in senso stretto chiamare direttamente altri thread.Se ad esempio il thread A provasse a chiamare direttamente il thread B, sarebbe un passaggio nello stack di chiamate del thread A!

Questo ci porta all'argomento della domanda: "non vengono sollevate eccezioni nei miei thread"

La ragione di ciò è che tutto ciò che fa un'eccezione è:

  • Registra l'errore
  • E rilassare il stack di chiamate.<--NB:La tua istanza TThread non può svolgere lo stack di chiamate del thread principale e non può interrompere arbitrariamente l'esecuzione del thread principale.

Quindi TThread non lo farà automaticamente segnala eccezioni alla tua applicazione principale.

Devi prendere la decisione esplicita su come desideri gestire gli errori nei thread e implementarli di conseguenza.

Soluzione

  • Il primo passaggio è lo stesso di un'applicazione con thread singolo.È necessario decidere quale sia l'errore significa e come dovrebbe reagire il thread.
    • Il thread deve continuare l'elaborazione?
    • Il thread dovrebbe interrompersi?
    • L'errore deve essere registrato/segnalato?
    • L'errore richiede una decisione dell'utente?<-- Questo è di gran lunga il più difficile da implementare, quindi per ora lo salteremo.
  • Una volta deciso ciò, implementare il gestore delle eccezioni appropriato.
  • TIP: Make sure the exception doesn't escape the thread. The OS won't like you if it does.
  • Se hai bisogno che il programma principale (thread) segnali l'errore all'utente, hai alcune opzioni.
    • Se il thread è stato scritto per restituire un oggetto risultato, allora è semplice:Apporta una modifica in modo che possa restituire l'errore in quell'oggetto se qualcosa è andato storto.
    • Invia un messaggio al thread principale per segnalare l'errore.Tieni presente che il thread principale implementa già un ciclo di messaggi, quindi la tua applicazione segnalerà l'errore non appena elaborerà quel messaggio.

MODIFICARE:Esempio di codice per il requisito indicato.

Se tutto ciò che vuoi fare è avvisare l'utente, allora La risposta di Cosmind Prunddovrebbe funzionare perfettamente per Delphi 2010.Le versioni precedenti di Delphi necessitano di un po' più di lavoro.Quanto segue è concettualmente simile a La risposta di Jeff, ma senza gli errori:

procedure TUpdaterThread.ShowException;
begin
  MessageDlg(FExceptionMessage, mtError, [mbOk], 0);
end;

procedure TUpdaterThread.Execute;
begin
  try

    raise Exception.Create('Test Exception');
    //The code for your thread goes here
    //
    //

  except
    //Based on your requirement, the except block should be the outer-most block of your code
    on E: Exception do
    begin
      FExceptionMessage := 'Exception: '+E.ClassName+'. '+E.Message;
      Synchronize(ShowException);
    end;
  end;
end;

Alcune importanti correzioni sulla risposta di Jeff, inclusa l'implementazione mostrata nella sua domanda:

La chiamata a Terminate è rilevante solo se il tuo thread è implementato all'interno di a while not Terminated do ...ciclo continuo.Dai un'occhiata a cosa Terminate il metodo effettivamente lo fa.

La chiamata a Exit è uno spreco inutile, ma probabilmente lo hai fatto a causa del tuo prossimo errore.

Nella tua domanda, stai racchiudendo ogni passaggio a sé stante try...except per gestire l'eccezione.Questo è un no-no assoluto!In questo modo fingi che, anche se si è verificata un'eccezione, tutto sia ok.Il tuo thread tenta il passaggio successivo, ma in realtà è garantito che fallirà!Questo è non il modo di gestire le eccezioni!

I thread non propagano automaticamente le eccezioni in altri thread. Quindi devi affrontarlo da solo.

Rafael ha delineato un approccio, ma ci sono alternative. La soluzione Rafael punta a trattare l'eccezione in modo sincrono maturandola nel thread principale.

In uno dei miei usi del threading, un pool di thread, i thread catturano e assumono la proprietà delle eccezioni. Ciò consente al thread di controllo di gestirli man mano che piace.

Il codice sembra così.

procedure TMyThread.Execute;
begin
  Try
    DoStuff;
  Except
    on Exception do begin
      FExceptAddr := ExceptAddr;
      FException := AcquireExceptionObject;
      //FBugReport := GetBugReportCallStackEtcFromMadExceptOrSimilar.
    end;
  End;
end;

Se il thread di controllo sceglie di aumentare l'eccezione, può farlo in questo modo:

raise Thread.FException at Thread.FExceptAddr;

A volte potresti avere un codice che non può chiamare la sincronizzazione, ad esempio alcune DLL e questo approccio è utile.

Nota che se non si aumenta l'eccezione catturata, è necessario distruggere altrimenti si ha una perdita di memoria.

Bene,

Sarà difficile senza il tuo codice sorgente, ma ho testato questo:

Come gestire le eccezioni negli oggetti Tthread

E funziona bene. Forse dovresti dare un'occhiata.

MODIFICARE:

Non stai seguendo ciò che i link che indichi ci dicono di fare. Controlla il mio link e vedrai come farlo.

EDIT 2:

Provalo e dimmi se ha funzionato:

 TUpdaterThread= class(TThread)
 private
   FException: Exception;
   procedure DoHandleException;
 protected
   procedure Execute; override;
   procedure HandleException; virtual;
 end;

procedure TUpdaterThread.Execute;
begin
  inherited;
  FreeOnTerminate := True;
  if Terminated then
    Exit;
  FileGrabber := THtmlExtractor.Create;
  HTTP := TIdHTTP.Create(Nil);
  try
    Try
      FileGrabber.Grab('http://jeffijoe.com/xSky/Updates/CheckForUpdates.php');
    Except
      HandleException;
    End;
    Try
      AppMajor := StrToInt(FileGrabber.ExtractValue('AppMajor[', ']'));
      AppMinor := StrToInt(FileGrabber.ExtractValue('AppMinor[', ']'));
      AppRelease := StrToInt(FileGrabber.ExtractValue('AppRelease[[', ']'));
    Except
      HandleException;
    End;
    if (APP_VER_MAJOR < AppMajor) or (APP_VER_MINOR < AppMinor) or (APP_VER_RELEASE < AppRelease) then begin
      VersionStr := Format('%d.%d.%d', [AppMajor, AppMinor, AppRelease]);
      UpdateText := 'Downloading Version ' + VersionStr;
      Synchronize(SyncUpdateLbl);
    end;
  finally
    FileGrabber.Free;
    HTTP.Free;
  end;
  Synchronize(SyncFinalize);

end;

procedure TUpdaterThread.HandleException;
begin
  FException := Exception(ExceptObject);
  try
    Synchronize(DoHandleException);
  finally
    FException := nil;
  end;
end;

procedure TMyThread.DoHandleException;
begin
  Application.ShowException(FException);
end;

EDIT 3:

Hai detto che non riesci a catturare eidhttpprotocolexception. Ma funziona per me. Prova questo campione e guardalo da solo:

procedure TUpdaterThread.Execute;
begin
  Try
    raise EIdHTTPProtocolException.Create('test');
  Except
    HandleException;
  End;
end;

In precedenza ho usato SendMessge per la comunicazione tra thread usando TWMCopyData, quindi penso che il seguente dovrebbe funzionare:

Const MyAppThreadError = WM_APP + 1;

constructor TUpdaterThread.Create(ErrorRecieverHandle: THandle);
begin
    Inherited Create(False);
    FErrorRecieverHandle := Application.Handle;
end;

procedure TUpdaterThread.Execute;
var
    cds: TWMCopyData;
begin
  try
     DoStuff;
  except on E:Exception do
    begin
        cds.dwData := 0;
        cds.cbData := Length(E.message) * SizeOf(Char);
        cds.lpData := Pointer(@E.message[1]);         
        SendMessage(FErrorRecieverHandle, MyAppThreadError, LPARAM(@cds), 0);
    end;
  end;
end;

L'ho usato solo per l'invio di semplici tipi di dati o stringhe, ma sono sicuro che potrebbe essere adattato a inviare maggiori informazioni, se necessario.

Avrai bisogno di Aggiungi Self.Handle al costruttore nella forma ha creato il thread e gestire il messaggio nella forma che lo ha creato

procedure HandleUpdateError(var Message:TMessage); message MyAppThreadError;
var
    StringValue: string;
    CopyData : TWMCopyData; 
begin
    CopyData := TWMCopyData(Msg);
    SetLength(StringValue, CopyData.CopyDataStruct.cbData div SizeOf(Char));
    Move(CopyData.CopyDataStruct.lpData^, StringValue[1], CopyData.CopyDataStruct.cbData);
    Message.Result := 0;
    ShowMessage(StringValue);
end;

Strano che tutti abbiano risposto a questa domanda, ma non sono riusciti a individuare l'ovvio problema: dato che le eccezioni sollevate in un thread di fondo sono asincroni e possono verificarsi in qualsiasi momento, ciò significa che mostrare le eccezioni da un thread di fondo aprirebbe una finestra di dialogo a caso Tempi all'utente, molto probabilmente mostrando un'eccezione che non ha nulla a che fare con ciò che l'utente sta facendo al momento. Dubito che farlo potrebbe migliorare l'esperienza dell'utente.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top