2013-09-27 161 views
7

Trong Dephi, tôi tạo ra một chủ đề, như thế này, mà sẽ gửi thông điệp tới hình thức chính trong từng thời kỳGửi chuỗi dữ liệu từ chủ đề đến hình thức chính

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; 

nơi WM_UPDATEDATA là một thông điệp tùy chỉnh, định nghĩa dưới đây:

const 
    WM_UPDATEDATA = WM_USER + 100; 

Và trong hình thức chính, nó sẽ làm như sau để cập nhật danh sách:

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

Tuy nhiên, khi Log chuỗi được gửi đến biểu mẫu chính là một biến cục bộ, biến này sẽ bị hủy sau khi gọi SendLog. Trong khi TForm1.WMUpdateData xử lý thông báo một cách không đồng bộ, vì vậy có thể khi nó được gọi, chuỗi Log đã bị hủy. Làm thế nào để giải quyết vấn đề này? Tôi nghĩ rằng có lẽ tôi có thể phân bổ không gian chuỗi trong một không gian hệ thống toàn cầu, và sau đó chuyển nó vào tin nhắn, sau đó sau khi TForm1.WMUpdateData xử lý thông báo, nó có thể phá hủy không gian chuỗi trong không gian toàn cục. Đó có phải là giải pháp khả thi không? Làm thế nào để thực hiện điều này?

Cảm ơn

+1

Look đây http://stackoverflow.com/questions/9932164/postmessage-lparam-truncation Hy vọng rằng nó sẽ giúp – Arkady

+0

Bạn cần phải khai báo biến Log của bạn như là một biến toàn cầu. –

+0

@ S.MAHDI Không! Điều gì sẽ xảy ra nếu có hai thư được xếp hàng đợi? –

Trả lời

0

Sử dụng SendMessage().

PostMessage() sẽ xử lý thư của bạn một cách không đồng bộ, về cơ bản nó đặt vào hàng đợi thư mục tiêu và Trả về ngay lập tức. Vào thời điểm mã trình xử lý truy cập dữ liệu được gửi trong wparam/lparam, người gọi của bạn đã giải phóng chuỗi đó.

Ngược lại, SendMessage() bỏ qua hàng đợi thư và gọi trực tiếp cửa sổ proc (đồng bộ). Vào thời điểm khi SendMessage() trả về, nó an toàn để giải phóng chuỗi.

+0

Tôi có thể thêm, nó không phải là giải pháp duy nhất và nó có thể không phù hợp trong mọi trường hợp resp. trường hợp cụ thể của bạn. Trong một số trường hợp, sử dụng Synchonize() có thể là một tùy chọn khác để xem xét. Về cơ bản nó phụ thuộc vào mức độ nghiêm ngặt mà bạn muốn (hoặc nên) kết hợp cả hai luồng với nhau. Cách tiếp cận thua cuộc nhất sẽ là một đối tượng trợ giúp được trả về được sử dụng chỉ cho trao đổi dữ liệu. Việc đếm ngược đảm bảo rằng một trong hai luồng là miễn phí để giải phóng nó trước mà không ảnh hưởng đến cái kia. Tất nhiên đối tượng trợ giúp cần khóa hoặc một số cơ chế khác để kiểm soát đồng thời. Và thậm chí còn có nhiều giải pháp khả thi hơn nữa ... – JensG

+0

Mọi người chọn PostMessage vì họ muốn phân phối không đồng bộ. Bạn bỏ qua điều này và đề xuất đồng bộ thay thế. Mà sẽ có tác động hiệu quả. Bởi vì bây giờ các luồng công nhân phải đợi cho luồng chính sẵn sàng gửi thông báo. –

+0

Và đó là lý do tại sao tôi đã thêm nhận xét rằng đó có thể không phải là giải pháp duy nhất và/hoặc tốt nhất. Chắc chắn bạn đã đọc này, phải không? Lập luận của bạn chỉ là suy đoán, trên hết. OP không hề viết về ý định của anh tại sao anh ta chọn PostMessage. – JensG

7

Ngoài thực tế là bạn đang đăng biến cục bộ, thuộc tính TWinControl.Handle cũng không an toàn. Thay vào đó, bạn nên sử dụng thuộc tính TApplication.Handle hoặc sử dụng AllocateHWnd() để tạo cửa sổ của riêng mình.

Bạn cần phải tự động cấp phát chuỗi trên heap, đăng con trỏ đó lên chuỗi chính và sau đó giải phóng bộ nhớ khi bạn sử dụng xong.

Ví dụ:

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; 

Hoặc:

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

Phân bổ dữ liệu và deallocating dựa trên postmessage không phải là một điều mà tôi muốn giới thiệu. Tin nhắn của bạn có thể không bao giờ được xử lý và sau đó bạn tạo ra một rò rỉ bộ nhớ khó phát hiện. Không có một sự xuất hiện duy nhất của mẫu này trong WINAPI có thể đưa ra một dấu hiệu, đó không phải là một ý tưởng hay. Ví dụ: kích thước hàng đợi tin nhắn bị giới hạn. Hoặc mã đích có thể thay đổi và giới thiệu rò rỉ bộ nhớ. Hoặc cửa sổ mục tiêu có thể xử lý WM_UPDATEDATA thông qua cửa sổ mặc định proc và do đó bộ nhớ không bao giờ được giải phóng. Và cứ thế ... – JensG

+0

@ JensG Đừng lo lắng. Đây là hình thức chính của cùng một ứng dụng. Tác giả của ứng dụng là kiểm soát việc xử lý 'WM_UPDATEDATA'. Không khó để xác minh rằng bộ nhớ được giải phóng. Và bạn đang lo lắng về một rò rỉ bộ nhớ khi hàng đợi của chủ đề chính của bạn trở nên đầy đủ ?! Đó là ít nhất lo lắng của bạn. Hàng đợi tin nhắn chính của tôi đã đầy, nhưng, OMG, tôi đã rò rỉ một số bộ nhớ !!!! –

+0

@ David Heffernan: Làm thế nào một giải pháp có một số hạn chế rõ ràng và mặc dù thực tế là có những giải pháp tốt hơn, mạnh mẽ hơn được cổ vũ theo cách đó là ngoài tầm hiểu của tôi. Và bất cứ ai từng theo dõi những rò rỉ bộ nhớ lạ trong một số ứng dụng máy chủ dự kiến ​​sẽ chạy hàng ngày và hàng tháng mà không bị gián đoạn mà thay vào đó là chết bởi các OOM đột ngột sẽ đồng ý với bình luận của tôi. Bạn không thể cẩn thận đủ về những thứ như vậy. Bạn rõ ràng đã không kinh nghiệm điều đó, bằng không bạn sẽ không nói theo cách đó. Nhưng tất nhiên nó là tuyệt vời cho bạn, vì vậy bạn có thể tỏa sáng khi nói đến tìm kiếm lỗi. – JensG

9

Nếu bạn có phiên bản D2009 hoặc phiên bản mới hơn, có một cách khác để gửi tin nhắn đến biểu mẫu chính của bạn. TThread.Queue là một cuộc gọi không đồng bộ từ một chuỗi, trong đó một phương thức hoặc thủ tục có thể được thực thi trong chuỗi chính.

Lợi thế ở đây là khung để thiết lập truyền thông điệp ít phức tạp hơn. Chỉ cần vượt qua phương thức gọi lại của bạn khi tạo chuỗi của bạn. Không xử lý và không xử lý rõ ràng việc phân bổ chuỗi/deallocation.

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;