2012-01-10 23 views
8

Trong một ví dụ cho tôi nhận được một ngoại lệ khi gọi AThread.Free.Chủ đề: Xử lý là không hợp lệ (6) khi cố gắng giải thoát một sợi lơ lửng

program Project44; 

{$APPTYPE CONSOLE} 

uses 
    SysUtils, Classes, Windows; 

type 
    TMyException = class(Exception); 

var 
    AThread: TThread; 
begin 
    AThread := TThread.Create(True); 
    try 
    AThread.FreeOnTerminate := True; 
    //I want to do some things here before starting the thread 
    //During the setup phase some exception might occur, this exception is for simulating purpouses 
     raise TMyException.Create('exception'); 
    except 
    AThread.Free; //Another exception here 
    end; 
end. 

Tôi có hai câu hỏi:

  1. Tôi nên làm cách nào miễn phí AThread bản sao của TThread trong một ví dụ cụ thể?

  2. Tôi không hiểu, tại sao TThread.Destroy đang gọi Resume trước khi hủy chính nó. điểm của việc này là gì?

+0

Trước tiên, bạn có nhận được bất kỳ lỗi/cảnh báo nào về điều này không? TThread.Execute là trừu tượng trong D2009. IME, bạn sẽ nhận được một cảnh báo về việc xây dựng các cá thể với các phương thức trừu tượng. Thông thường, TThread.Execute được ghi đè trong lớp hậu duệ TThread và đó là hậu duệ được khởi tạo. Tôi đã không bao giờ cố gắng để tạo ra một thể hiện của TThread trực tiếp - Tôi khá chắc chắn rằng một số ngoại lệ sẽ được nâng lên trên thread xây dựng, các thread xây dựng hoặc cả hai. –

+4

Bạn có thể đợi để đặt 'FreeOnTerminate' cho đến trước khi bạn gọi' Tiếp tục'. –

+0

Tôi đoán hủy các cuộc gọi tiếp tục vì nếu một sợi bị treo thì nó không thể bị phá hủy đúng cách trong trạng thái như vậy. –

Trả lời

15

Bạn không thể đặt FreeOnTerminate-True gọi Free trên dụ chủ đề. Bạn phải làm một cái khác, nhưng không phải cả hai. Vì nó đứng mã của bạn phá hủy các chủ đề hai lần. Bạn không bao giờ phải phá hủy một đối tượng hai lần và tất nhiên khi destructor chạy lần thứ hai, các lỗi xảy ra.

Điều xảy ra ở đây là vì bạn đã tạo chuỗi bị treo, không có gì xảy ra cho đến khi bạn giải phóng hoàn toàn chuỗi. Khi bạn làm điều đó, destructor sẽ tiếp tục lại luồng, đợi nó hoàn tất. Điều này sau đó dẫn đến việc Free được gọi lại vì bạn đặt FreeOnTerminate thành True. Lệnh gọi thứ hai này để Free đóng tay cầm. Sau đó, bạn quay lại chủ đề proc và gọi số ExitThread. Điều này không thành công vì xử lý của chuỗi đã bị đóng.

Như Martin chỉ ra trong nhận xét bạn không được tạo TThread trực tiếp vì phương pháp TThread.Execute là trừu tượng. Ngoài ra, bạn không nên sử dụng Resume không được dùng nữa. Sử dụng Start để bắt đầu thực hiện một chuỗi bị treo.

Cá nhân tôi không thích sử dụng FreeOnTerminate. Sử dụng tính năng này dẫn đến việc chuỗi bị hủy trên một chuỗi khác mà từ đó nó được tạo ra. Bạn thường sử dụng nó khi bạn muốn quên tham chiếu cá thể. Điều đó sau đó khiến bạn không chắc chắn liệu chuỗi có bị hủy hay không khi quá trình của bạn chấm dứt, hoặc thậm chí là nó chấm dứt và giải phóng chính nó trong quá trình chấm dứt quá trình.

Nếu bạn phải sử dụng FreeOnTerminate thì bạn cần đảm bảo rằng bạn không gọi Free sau khi đã đặt FreeOnTerminate thành True. Vì vậy, giải pháp hiển nhiên là đặt FreeOnTerminate thành True ngay trước khi gọi Start và sau đó quên về phiên bản chuỗi. Nếu bạn có bất kỳ ngoại lệ nào trước khi bạn sẵn sàng bắt đầu thì bạn có thể giải phóng một cách an toàn chuỗi đó vì bạn FreeOnTerminate vẫn sẽ là False tại thời điểm đó.

Thread := TMyThread.Create(True); 
Try 
    //initialise thread object 
Except 
    Thread.Free; 
    raise; 
End; 
Thread.FreeOnTerminate := True; 
Thread.Start; 
Thread := nil; 

Cách tiếp cận thanh lịch hơn là di chuyển tất cả việc khởi tạo vào công cụ xây dựng TMyThread. Sau đó, mã sẽ trông giống như sau:

Thread := TMyThread.Create(True); 
Thread.FreeOnTerminate := True; 
Thread.Start; 
Thread := nil; 
+0

Tôi đã nghĩ về điều đó.Bạn có lẽ đúng, nhưng Delphi chủ đề kiểm soát, đặc biệt là với chấm dứt, đã được, (và có lẽ vẫn còn), như một mớ hỗn độn mà tôi đã không dám đăng nó. Tôi nhìn rất ngắn gọn tại TThread trong 'lớp học' và quyết định không nhìn sâu hơn nữa trong trường hợp tôi tìm thấy thứ gì đó. –

+0

@MartinJames Đây chính là điều xảy ra. Trình phá hủy chạy hai lần. Điều đó không bao giờ kết thúc tốt đẹp. –

+0

@David Nếu tôi không gọi AThread.Free trong ví dụ cụ thể này, tôi sẽ bị rò rỉ bộ nhớ. Vậy làm thế nào tôi có thể giải phóng thread hai lần? – Wodzu

5

Tình huống rất phức tạp trong trường hợp của bạn.

Trước tiên, bạn không thực sự giải phóng một chuỗi bị treo; a thread thi đấu trở lại trong destructor:

begin 
    Terminate; 
    if FCreateSuspended then 
     Resume; 
    WaitFor; 
    end; 

Kể từ Terminate được gọi trước khi Resume, phương pháp Execute không bao giờ chạy, và chủ đề chấm dứt ngay lập tức sau khi được nối lại:

try 
    if not Thread.Terminated then 
    try 
     Thread.Execute; 
    except 
     Thread.FFatalException := AcquireExceptionObject; 
    end; 
    finally 
    Result := Thread.FReturnValue; 
    FreeThread := Thread.FFreeOnTerminate; 
    Thread.DoTerminate; 
    Thread.FFinished := True; 
    SignalSyncEvent; 
    if FreeThread then Thread.Free; 

Bây giờ nhìn vào dòng cuối cùng - bạn gọi destructor (Thread.Free) từ destructor chính nó! Lỗi tuyệt vời!


Để trả lời câu hỏi của bạn:

  1. Bạn chỉ không thể sử dụng FreeOnTerminate:= True trong mã của bạn;
  2. Bạn nên hỏi Embarcadero tại sao TThread được thiết kế như vậy; tôi đoán - một số mã (DoTerminate phương pháp) nên được thực hiện trong bối cảnh chủ đề trong khi chuỗi kết thúc.

Bạn có thể gửi yêu cầu tính năng để QC: thêm FFreeOnTerminate:= False-TThread.Destroy thực hiện:

destructor TThread.Destroy; 
begin 
    FFreeOnTerminate:= False; 
// everything else is the same 
    .. 
end; 

Điều đó sẽ ngăn chặn cuộc gọi desctructor đệ quy và làm cho mã của bạn hợp lệ.

+0

Cảm ơn bạn Serg, về điểm 1: Tôi tin rằng tôi có thể, nếu tôi di chuyển nó ngay trước khi Resume() hoặc thậm chí ngay sau đó, nó sẽ hoạt động. Nhưng nhờ câu trả lời anyway, tôi hiểu vấn đề tốt hơn bây giờ. +1 – Wodzu

+0

Sau khi bạn gọi 'Bắt ​​đầu' (vui lòng gọi' Bắt đầu' thay vì 'Tiếp tục') không đúng. Các chủ đề có thể hoàn thành và sau đó nó sẽ là quá muộn để thiết lập 'FreeOnTerminate'. Sau đó, bạn sẽ bị rò rỉ luồng và xử lý hệ điều hành. –

+1

+1, @David, không có 'TThread.Start' trong D2009 (như thẻ Q được gắn thẻ);) Kể từ D2010, vì vậy' TThread.Resume' ở đây là chính xác. – TLama

Các vấn đề liên quan