2013-09-27 22 views
7

In Dephi, creo un filo, come questo, che invierà un messaggio al modulo principale di tanto in tantoi dati di invio stringa a partire da fili a forma principale

Procedure TMyThread.SendLog(I: Integer); 
Var 
    Log: array[0..255] of Char; 
Begin 
    strcopy(@Log,PChar('Log: current stag is ' + IntToStr(I))); 
    PostMessage(Form1.Handle,WM_UPDATEDATA,Integer(PChar(@Log)),0); 
End; 

procedure TMyThread.Execute; 
var 
    I: Integer; 
begin 
    for I := 0 to 1024 * 65536 do 
    begin 
    if (I mod 65536) == 0 then 
    begin 
     SendLog(I); 
    End; 
    End; 
end; 

dove WM_UPDATEDATA è un messaggio personalizzato, definito di seguito:

const 
    WM_UPDATEDATA = WM_USER + 100; 

E in forma principale, lo farà come segue per aggiornare l'elenco:

procedure TForm1.WMUpdateData(var msg : TMessage); 
begin 
    List1.Items.Add(PChar(msg.WParam)); 
end; 

Tuttavia, come il registro la stringa inviata al modulo principale è una variabile locale, che verrà distrutta dopo aver chiamato SendLog. Mentre TForm1.WMUpdateData elabora il messaggio in modo asincrono, è possibile che quando viene richiamato, la stringa Log sia già stata distrutta. Come risolvere questo problema?

Penso che forse posso allocare lo spazio di stringa in uno spazio di sistema globale e quindi passarlo al messaggio, quindi dopo TForm1.WMUpdateData elabora il messaggio, esso può distruggere lo spazio di stringa nello spazio globale. È una soluzione praticabile? Come implementarlo?

Grazie

+1

Guardate qui http://stackoverflow.com/questions/9932164/postmessage-lparam-truncation Speriamo che aiuta – Arkady

+0

È necessario dichiarare la variabile Log come una variabile globale. –

+0

@ S.MAHDI No! Cosa succede se ci sono due messaggi in coda? –

risposta

0

Utilizzare SendMessage().

PostMessage() elaborerà il messaggio in modo asincrono, in pratica inserisce nella coda messaggi di destinazione e restituisce immediatamente. Nel momento in cui il codice del gestore accede ai dati inviati in wparam/lparam, il chiamante ha già liberato la stringa.

Al contrario, SendMessage() ignora la coda messaggi e chiama direttamente la finestra proc (in modo sincrono). Nel momento in cui SendMessage() restituisce, è sicuro liberare la stringa.

+0

Posso aggiungere, non è l'unica soluzione e potrebbe non essere adatta in tutti i casi risp. il tuo caso specifico. In alcuni casi, l'uso di Synchonize() può essere un'altra opzione da considerare. Dipende fondamentalmente da quanto desideri (o dovresti) stringere i due fili insieme. L'approccio più sfavorevole sarebbe un oggetto helper conteggiato utilizzato esclusivamente per lo scambio di dati. Il Refcount si assicura che entrambi i thread siano liberi di rilasciarli prima senza influenzare l'altro. Ovviamente l'oggetto helper necessita di un lock o di qualche altro meccanismo per controllare la concorrenza. E ci sono ancora più soluzioni possibili ... – JensG

+0

Le persone scelgono PostMessage perché vogliono una consegna asincrona. Ignoralo e suggerisci invece sincrono. Quale avrà implicazioni di prestazione. Perché ora i thread di lavoro devono attendere che il thread principale sia pronto per inviare il messaggio. –

+0

Ed è per questo che ho aggiunto il commento che potrebbe non essere l'unica e/o la migliore soluzione. Sicuramente hai letto questo, vero? Il tuo argomento è solo speculativo, oltre a quello. L'OP non ha scritto da nessuna parte sulle sue intenzioni perché ha scelto PostMessage. – JensG

7

Oltre al fatto che si sta postando una variabile locale, la proprietà TWinControl.Handle non è thread-safe. Dovresti invece utilizzare la proprietà TApplication.Handle oppure utilizzare AllocateHWnd() per creare la tua finestra.

È necessario allocare dinamicamente la stringa nell'heap, postare il puntatore nel thread principale e quindi liberare la memoria quando si è terminato di utilizzarlo.

Ad esempio:

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    Application.OnMessage := AppMessage; 
    // or use a TApplicationEvents component... 
end; 

procedure TForm1.FormDestroy(Sender: TObject); 
begin 
    Application.OnMessage := nil; 
end; 

procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean); 
var 
    S: PString; 
begin 
    if Msg.Message = WM_UPDATEDATA then 
    begin 
    S := PString(msg.LParam); 
    try 
     List1.Items.Add(S^); 
    finally 
     Dispose(S); 
    end; 
    Handled := True; 
    end; 
end; 

procedure TMyThread.SendLog(I: Integer); 
var 
    Log: PString; 
begin 
    New(Log); 
    Log^ := 'Log: current stag is ' + IntToStr(I); 
    if not PostMessage(Application.Handle, WM_UPDATEDATA, 0, LPARAM(Log)) then 
    Dispose(Log); 
end; 

alternativa:

var 
    hLogWnd: HWND = 0; 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    hLogWnd := AllocateHWnd(LogWndProc); 
end; 

procedure TForm1.FormDestroy(Sender: TObject); 
begin 
    if hLogWnd <> 0 then 
    DeallocateHWnd(hLogWnd); 
end; 

procedure TForm1.LogWndProc(var Message: TMessage); 
var 
    S: PString; 
begin 
    if Message.Msg = WM_UPDATEDATA then 
    begin 
    S := PString(msg.LParam); 
    try 
     List1.Items.Add(S^); 
    finally 
     Dispose(S); 
    end; 
    end else 
    Message.Result := DefWindowProc(hLogWnd, Message.Msg, Message.WParam, Message.LParam); 
end; 

procedure TMyThread.SendLog(I: Integer); 
var 
    Log: PString; 
begin 
    New(Log); 
    Log^ := 'Log: current stag is ' + IntToStr(I); 
    if not PostMessage(hLogWnd, WM_UPDATEDATA, 0, LPARAM(Log)) then 
    Dispose(Log); 
end; 
+3

L'allocazione dei dati e la deallocazione basata su post-messaggio non è una cosa che consiglierei. Il tuo messaggio potrebbe non essere mai elaborato e quindi creerai una perdita di memoria difficile da rilevare. Non avere una singola indicazione di questo modello nel WINAPI può dare un'indicazione, non è una buona idea. Ad esempio, le dimensioni della coda dei messaggi sono limitate. O il codice di destinazione potrebbe cambiare e introdurre la perdita di memoria. Oppure la finestra di destinazione potrebbe gestire WM_UPDATEDATA tramite il proc di finestra predefinito e quindi la memoria non viene mai liberata. E così via ... – JensG

+0

@JensG Non preoccuparti. Questa è la forma principale della stessa app. L'autore dell'app ha il controllo della gestione di 'WM_UPDATEDATA'. Non è difficile verificare che la memoria sia stata liberata. E sei preoccupato per una perdita di memoria quando la coda del thread principale diventa piena ?! Questa è l'ultima delle tue preoccupazioni. La mia coda di messaggi thread principale è piena, ma, OMG, ho perso un po 'di memoria !!!! –

+0

@David Heffernan: come una soluzione che presenta diversi ovvi inconvenienti e nonostante il fatto che ci siano soluzioni migliori e più robuste è applaudita in questo modo è al di là della mia comprensione. E chiunque abbia mai rintracciato strane perdite di memoria in qualche app del server che dovrebbe funzionare per giorni e mesi senza interruzioni, ma che invece muore di OOM improvvisate, accetterà il mio commento. Non puoi fare abbastanza attenzione su queste cose. Ovviamente non l'hai mai provato, altrimenti non avresti parlato in quel modo. Ma naturalmente è fantastico per te, quindi puoi brillare quando si tratta di cercare i bug. – JensG

9

Se si dispone della versione D2009 o successiva, esiste un altro modo per inviare messaggi al modulo principale. TThread.Queue è una chiamata asincrona da un thread, in cui è possibile eseguire un metodo o una procedura nel thread principale.

Il vantaggio è che il frame per impostare il passaggio dei messaggi è meno complesso. Basta passare il metodo di callback durante la creazione del thread. Nessuna maniglia e nessuna gestione esplicita dell'assegnazione/deallocazione di stringhe.

Type 
    TMyCallback = procedure(const s : String) of object; 

    TMyThread = class(TThread) 
    private 
     FCallback : TMyCallback; 
     procedure Execute; override; 
     procedure SendLog(I: Integer); 
    public 
     constructor Create(aCallback : TMyCallback); 
    end; 

constructor TMyThread.Create(aCallback: TMyCallback); 
begin 
    inherited Create(false); 
    FCallback := aCallback; 
end; 

procedure TMyThread.SendLog(I: Integer); 
begin 
    if not Assigned(FCallback) then 
    Exit; 
    Self.Queue( // Executed later in the main thread 
    procedure 
    begin 
     FCallback('Log: current stag is ' + IntToStr(I)); 
    end 
); 
end; 

procedure TMyThread.Execute; 
var 
    I: Integer; 
begin 
    for I := 0 to 1024 * 65536 do 
    begin 
    if ((I mod 65536) = 0) then 
    begin 
     SendLog(I); 
    End; 
    End; 
end; 

procedure TMyForm.TheCallback(const msg : String); 
begin 
    // Show msg 
end; 

procedure TMyForm.StartBackgroundTask(Sender : TObject); 
begin 
    ... 
    FMyThread := TMyThread.Create(TheCallback); 
    ... 
end;