線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:3896
推到 Plurk!
推到 Facebook!

多線程中COM控件無法Free掉的問題?

答題得分者是:jow
h@visli
資深會員


發表:103
回覆:429
積分:431
註冊:2004-02-13

發送簡訊給我
#1 引用回覆 回覆 發表時間:2007-09-14 18:17:45 IP:219.133.xxx.xxx 未訂閱
在子線程中,創建了一個ActiveX對象,當線程終止時,需要Free掉這個對象,但程序卻停止在Free這條語句上,請教大家了

單元一:
[code delphi]
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Edit1: TEdit;
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

uses Unit2;

{$R *.dfm}

var
MyThd: TMyThread;

procedure TForm1.FormCreate(Sender: TObject);
begin
Randomize;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
if Assigned(MyThd) then
begin
Button2Click(nil);
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
MyThd := TMyThread.Create;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
MyThd.Terminate;
MyThd.WaitFor;
FreeAndNil(MyThd);
end;


end.

[/code]

單元二:

[code delphi]
unit Unit2;

interface

uses classes, SysUtils, Windows, OleCtrls, ActiveX, VCFI;

type
TMyThread = class(TThread)
private
procedure RefreshTime;
protected
procedure Execute; override;
public
constructor Create;
end;

implementation

uses Unit1;

constructor TMyThread.Create;
begin
FreeOnTerminate := False;
inherited Create(False);
end;

procedure TMyThread.Execute;
var
vt: TVtChart; //TVtChart is on the 'ActivX' Palette.
begin
CoInitializeEx(nil, COINIT_MULTITHREADED);
VT := TVtChart.Create(nil);
while not Terminated do
begin
Synchronize(RefreshTime);
Sleep(35);
end;
Sleep(100);
vt.Free; //<---------The flow will Stop in here
CoUninitialize;
end;

procedure TMyThread.RefreshTime;
begin
Form1.Edit1.Text := DateTimeToStr(now) IntToStr(Random(991000));
end;

end.

[/code]

原碼文件:
------
------------------------
博采眾家之長,奉獻綿薄之力
------------------------
jow
尊榮會員


發表:66
回覆:751
積分:1253
註冊:2002-03-13

發送簡訊給我
#2 引用回覆 回覆 發表時間:2007-09-15 01:05:16 IP:203.79.xxx.xxx 訂閱
重新實作程式執行正常,謹供參考.

http://delphi.ktop.com.tw/board.php?cid=31&fid=79&tid=90454
dllee
站務副站長


發表:321
回覆:2519
積分:1711
註冊:2002-04-15

發送簡訊給我
#3 引用回覆 回覆 發表時間:2007-09-15 07:32:34 IP:59.105.xxx.xxx 訂閱
對不起插個花  因為覺得有趣,所以討論一下。

jow 的程式與 h@visli 主要不同的部分:
[code delphi]
procedure TMyThread.Execute1;
var
VT: TVtChart;
begin
CoInitializeEx(nil, COINIT_MULTITHREADED);
try
VT := TVtChart.Create(nil);
try
while not Terminated do
begin
Synchronize(RefreshTime);
Sleep(35);
end;
Sleep(100);
finally
FreeAndNil(Vt);
end;
finally
CoUninitialize;
end;
end;
[/code]
除了 try, finally 外,就是使用了 FreeAndNil 的不同,
那是兩個都要用才有效嗎?還是使用 FreeAndNil 就好?
按理 finally 是一定會執行到的,而原本 h@visli 的問題是
Terminate 時執行到 vt.Free 會當,表示那一行有執行到,
所以,只要 FreeAndNil 就可以了(個人簡單的推論,也許有誤)。
但 FreeAndNil 不就是執行 vt.Free 並設 vt:=Nil 嗎?
頂多前面判斷 vt 是否已為 Nil,原 vt.Free 會當除非是當時已是 Nil ?
另外,兩位還有一點不同,就是 Delphi 的版本,
h@visli 使用的是 D7 ,而 jow 使用的是 D6


強力推薦 ShareMe 免費網路硬碟 VMASK VMIO-Server/SECS/GEM dllee's blog dllee's StatPlus
------
http://www.ViewMove.com
jow
尊榮會員


發表:66
回覆:751
積分:1253
註冊:2002-03-13

發送簡訊給我
#4 引用回覆 回覆 發表時間:2007-09-17 12:39:28 IP:210.66.xxx.xxx 訂閱
其實 h@visli 所附的程式碼在D6出現一樣的問題,而且也和
是VT.Free 或 FreeAndNil(VT) 沒有關係. 我也是很好奇才稍微
重新實作了一遍,當然依照個人的程式習慣(主要是TThread方面).

程式中除了副站長 所提的差異之外,其實還包括了以下幾點,
先聲明以下差異,我認為應該不致影響 h@visli 和 我的範例出現
不同的執行結果,這也是我好奇的地方.

(1)TMyThread.Create(AOwner: TForm1; Index: Integer);
會做這樣的改變是去除 class TMyThread() 中對特定變數的引用,

Form1.Edit1.Text := DateTimeToStr(now) IntToStr(Random(991000));

當然在Form1自動被產生的情況下,上述程式碼不至於產生問題. 純粹是個人
寫程式的習慣.

(2)MyThread 釋放的時機:
h@visli 兄用的是WaitFor(), 而個人習慣上是等Thread結束Execute中
的迴圈之後,再於其引發的 TThread.OnTerminate() 事件中, 依據
原先設定的 TThread.FreeOnTerminate 去決定是否需要作Free的動作
並將 MyThread 變數重新設為 nil.

這也是個人習慣, 無關程式執行結果.

(3) 執行緒變數宣告的地方不同:
h@visli 兄將MyThread 宣告在 Unit1 的 implementation 之下, 意謂著
只有在 Unit1 的 implementation 區段下的程式碼才可引用 MyThread變數,
可是就h@visli 兄目前程式碼中使用的範圍,也不至於產生問題.


綜合上述幾項不至於影響執行結果的差異,那麼差異有限的
兩支程式為何有不同的執行結果?

我個人也很好奇.

編輯記錄
jow 重新編輯於 2007-09-17 12:40:44, 註解 無‧
dllee
站務副站長


發表:321
回覆:2519
積分:1711
註冊:2002-04-15

發送簡訊給我
#5 引用回覆 回覆 發表時間:2007-09-17 12:46:53 IP:220.134.xxx.xxx 訂閱
是呀, 我眼花了, 沒注意到 .Create 的不同 

還是請 h@visli 測試一下吧,如果可以,回覆一下測試結果.... 我真的很好奇耶...



強力推薦 ShareMe 免費網路硬碟VMASKVMIO-Server/SECS/GEMdllee's blogdllee's StatPlus
------
http://www.ViewMove.com
h@visli
資深會員


發表:103
回覆:429
積分:431
註冊:2004-02-13

發送簡訊給我
#6 引用回覆 回覆 發表時間:2007-09-17 16:51:02 IP:219.133.xxx.xxx 未訂閱
謝謝兩位的回復,因爲這兩天太忙了所以沒有來得及回復。

我先說一下jow的代碼在我這裡運行的結果(我衹有D7):
1、不管是TMyThread的Execute0還是Execute1,在執行後,點擊btnTerminate0按鈕,
程序會停止在DoOnMyThreadTerminated過程的FreeAndNil(MyThread)這句代碼上,
無法繼續執行:
[code delphi]
procedure TForm1.DoOnMyThreadTerminated(Sender: TObject);
begin
if MyThread <> nil then
begin
Label1.Caption := 'MyThread Terminated, Index = '
IntToStr(MyThread.FIndex);
FreeAndNil(MyThread);
end;
end;
[/code]
原因我也正在查找,不知jow在D6上測試是否出現同樣現象?

2、jow的Execute1執行體是可以正常執行的,即FreeAndNil(Vt)與CoUninitialize兩句都
能正常執行。這是因為jow的TMyThread類是直接調用MyThread.Terminate來標志線程終止,
然後而在DoOnMyThreadTerminated中Free線程對象:
[code delphi]
procedure TForm1.btnTerminate0Click(Sender: TObject);
begin
if MyThread <> nil then
MyThread.Terminate;
end;
[/code]

但如果是改用
[code delphi]
procedure TForm1.btnTerminate0Click(Sender: TObject);
begin
if MyThread <> nil then
begin
MyThread.Terminate;
MyThread.Waitfor;
MyThread.Free; //(即不在DoOnMyThreadTerminated中Free線程對象)
end;
end;
[/code]
這時Execute1也就不能正常執行完成了,會停止在FreeAndNil(Vt);這句上。
------
------------------------
博采眾家之長,奉獻綿薄之力
------------------------
編輯記錄
yckuo 重新編輯於 2007-09-17 17:53:41, 註解 套用程式碼高亮顯示‧
h@visli
資深會員


發表:103
回覆:429
積分:431
註冊:2004-02-13

發送簡訊給我
#7 引用回覆 回覆 發表時間:2007-09-17 17:29:15 IP:219.133.xxx.xxx 未訂閱
另外,再說一下我的代碼。

我知道,有兩種Free線程的方法:
1、設置線程的FreeOnTerminate屬性爲False, 然後由我們自己來Free線程對象;
2、設置線程的FreeOnTerminate屬性爲True, 由線程對象在執行完Execute方法後,在
ThreadProc函數中自動Free。

我選用的是第一種方法,因為我的這個線程是在App關閉時在MainFrm的OnDestroy事件中
釋放的。如果採用第二種方法,存在一個問題,即App關閉(主線程)的速度比我這個線程釋放要快,
會造成內存Leak。所以我需要使用WaitFor這個方法,讓主线程來等待子线程Free完畢。

現在的問題就是使用了WaitFor這個方法後,ActiveX控件TVtChart在Free時就死掉了(換用其它ActivX控件都一樣)!

我前天跟蹤至TOleControl的析构函數:
destructor TOleControl.Destroy;
發現是停止在這個Destroy裡面。

可惜我的Delphi7今天不知出了什麽問題,今天不能在OleCtrls單元設置斷點了,
盡管我開啓了Use Debug DCUs這個編譯選項。
------
------------------------
博采眾家之長,奉獻綿薄之力
------------------------
jow
尊榮會員


發表:66
回覆:751
積分:1253
註冊:2002-03-13

發送簡訊給我
#8 引用回覆 回覆 發表時間:2007-09-17 18:03:33 IP:203.79.xxx.xxx 訂閱
我的程式碼在D6執行正常.
不知道有沒有其他使用D6的人回應,
是否可以正常執行? 或是有Bug?

補充說明,目前FreeOnTerminate 設為 False.
也就是說釋放MyThread的動作是在TForm1.DoOnMyThreadTerminated()中,


[code delphi]
constructor TMyThread.Create(AOwner: TForm1; Index: Integer);
begin
inherited Create(False);
FOwner := AOwner;
FIndex := Index;
FreeOnTerminate := False;
end;

[/code]




[code delphi]
procedure TForm1.DoOnMyThreadTerminated(Sender: TObject);
begin
if MyThread <> nil then
begin
Label1.Caption := 'MyThread Terminated, Index = '
IntToStr(MyThread.FIndex);
FreeAndNil(MyThread);
end;
end;
[/code]

編輯記錄
jow 重新編輯於 2007-09-17 18:04:01, 註解 無‧
jow 重新編輯於 2007-09-17 19:26:19, 註解 無‧
jow 重新編輯於 2007-09-17 19:29:11, 註解 無‧
jow 重新編輯於 2007-09-17 19:30:13, 註解 無‧
wameng
版主


發表:31
回覆:1336
積分:1188
註冊:2004-09-16

發送簡訊給我
#9 引用回覆 回覆 發表時間:2007-09-17 23:41:40 IP:61.31.xxx.xxx 訂閱
initialization
CoInitializeEx(nil, COINIT_MULTITHREADED);

finalization
CoUninitialize;
end.

不要懷疑就是這樣。
我也不清楚原因。
jow
尊榮會員


發表:66
回覆:751
積分:1253
註冊:2002-03-13

發送簡訊給我
#10 引用回覆 回覆 發表時間:2007-09-18 00:49:45 IP:203.79.xxx.xxx 訂閱
<textarea class="delphi" rows="10" cols="60" name="code"> procedure TMyThread.Execute1; var VT: TVtChart; begin ////////// CoInitializeEx(nil, COINIT_MULTITHREADED); try VT := TVtChart.Create(nil); try while not Terminated do begin Synchronize(RefreshTime); Sleep(35); end; Sleep(100); finally FreeAndNil(Vt); end; finally ////////// CoUninitialize; end; end; procedure TForm1.btnTerminate1Click(Sender: TObject); var Timeout: Cardinal; IsTimeout: Boolean; begin if MyThread <> nil then begin MyThread.OnTerminate := nil; IsTimeout := False; Timeout := GetTickCount 5000; MyThread.Terminate; while not (MyThread.Terminated or IsTimeout)do begin IsTimeout := GetTickCount > timeout; Application.ProcessMessages; Sleep(10); end; if IsTimeout then Label1.Caption := 'MyThread Termination Timeout' else if MyThread.Terminated then Label1.Caption := '(NEW)MyThread Terminated, Index = ' IntToStr(MyThread.FIndex); MyThread.Free; MyThread := nil; end; end; procedure TForm1.btnTerminate2Click(Sender: TObject); begin if MyThread <> nil then begin MyThread.OnTerminate := nil;//取消 MyThread.Terminate; MyThread.WaitFor; if MyThread.Terminated then Label1.Caption := '(NEW)MyThread Terminated, Index = ' IntToStr(MyThread.FIndex); MyThread.Free; MyThread := nil; end; end; initialization CoInitializeEx(nil, COINIT_MULTITHREADED); finalization CoUninitialize; </textarea>

wameng 建議, 修改程式碼.
在D6測試, 三個按鍵執行正常.


jow
尊榮會員


發表:66
回覆:751
積分:1253
註冊:2002-03-13

發送簡訊給我
#11 引用回覆 回覆 發表時間:2007-09-18 01:03:05 IP:203.79.xxx.xxx 訂閱
dllee
站務副站長


發表:321
回覆:2519
積分:1711
註冊:2002-04-15

發送簡訊給我
#12 引用回覆 回覆 發表時間:2007-09-18 07:40:33 IP:59.105.xxx.xxx 訂閱
哇,如果是這樣的解法... 在 BCB 下就麻煩了,
因為 BCB 每個 unit 沒有 initialization, finalization 可用

看了 jow 的 code 讓我更了解 Thread 的用法

感謝分享。



強力推薦 Seednet ShareMe 至少 2G 免費網路硬碟, 最大特點:放檔後不使用不會砍檔
------
http://www.ViewMove.com
h@visli
資深會員


發表:103
回覆:429
積分:431
註冊:2004-02-13

發送簡訊給我
#13 引用回覆 回覆 發表時間:2007-09-18 09:29:47 IP:219.133.xxx.xxx 未訂閱
wameng版主的代碼果然有效!但這是爲什麽呢?

另外, Jow老兄的FreeAndNil(MyThread); 這句(btnTerminate0Click)我在D2007下調試時,也與D7下一樣停止執行下去。
我分析了一下Classes裡的線程執行過程:
[code delphi]
function ThreadProc(Thread: TThread): Integer;
var
FreeThread: Boolean;
begin
try
if not Thread.Terminated then
try
Thread.Execute;
except
Thread.FFatalException := AcquireExceptionObject;
end;
finally
FreeThread := Thread.FFreeOnTerminate;
Result := Thread.FReturnValue;
Thread.DoTerminate;
Thread.FFinished := True;
SignalSyncEvent;
if FreeThread then Thread.Free;
EndThread(Result);
end;
end;

[/code]

請注意 Thread.DoTerminate這句,Jow兄的DoOnMyThreadTerminated代碼就是由 Thread.DoTerminate
調用,且在主線程中執行,而Jow兄在這裡FreeAndNil(MyThread),而ThreadProc函數後面的代碼還要用到
MyThread這個對象,所以我們不能在Thread.DoTerminate這裡就Free線程對象的。

另外,Jow兄的btnTerminate1Click與btnTerminate2Click中都有先MyThread.Terminate;再判斷MyThread.Terminated
。其實這種判斷是沒必要的,因為MyThread.Terminate就是把線程的Terminated屬性置爲True(主線程中),
所以沒有必要再判斷。


===================引 用 wameng 文 章===================
initialization
CoInitializeEx(nil, COINIT_MULTITHREADED);

finalization
CoUninitialize;
end.

不要懷疑就是這樣。
我也不清楚原因。
------
------------------------
博采眾家之長,奉獻綿薄之力
------------------------
編輯記錄
h@visli 重新編輯於 2007-09-18 09:40:25, 註解 無‧
h@visli 重新編輯於 2007-09-18 10:15:04, 註解 無‧
jow
尊榮會員


發表:66
回覆:751
積分:1253
註冊:2002-03-13

發送簡訊給我
#14 引用回覆 回覆 發表時間:2007-09-18 10:58:47 IP:211.76.xxx.xxx 訂閱
這是D6版的code

不同點在於順序問題
Thread.FFinished := True;
Thread.DoTerminate;
if FreeThread then Thread.Free;//////FreeOnTerminate := False;

另外 D7 多了一項 SignalSyncEvent;

<textarea class="delphi" rows="10" cols="60" name="code">function ThreadProc(Thread: TThread): Integer; var FreeThread: Boolean; begin {$IFDEF LINUX} if Thread.FSuspended then sem_wait(Thread.FCreateSuspendedSem); {$ENDIF} try if not Thread.Terminated then try Thread.Execute; except Thread.FFatalException := AcquireExceptionObject; end; finally FreeThread := Thread.FFreeOnTerminate; Result := Thread.FReturnValue; Thread.FFinished := True; Thread.DoTerminate; if FreeThread then Thread.Free; {$IFDEF MSWINDOWS} EndThread(Result); {$ENDIF} {$IFDEF LINUX} // Directly call pthread_exit since EndThread will detach the thread causing // the pthread_join in TThread.WaitFor to fail. Also, make sure the EndThreadProc // is called just like EndThread would do. EndThreadProc should not return // and call pthread_exit itself. if Assigned(EndThreadProc) then EndThreadProc(Result); pthread_exit(Pointer(Result)); {$ENDIF} end; end; </textarea>
編輯記錄
jow 重新編輯於 2007-09-18 11:01:08, 註解 無‧
h@visli
資深會員


發表:103
回覆:429
積分:431
註冊:2004-02-13

發送簡訊給我
#15 引用回覆 回覆 發表時間:2007-09-18 11:05:07 IP:219.133.xxx.xxx 未訂閱
看來是版本不同,原代碼有變更了。謝謝Jow、dlleewameng
------
------------------------
博采眾家之長,奉獻綿薄之力
------------------------
mitchellhu
一般會員


發表:23
回覆:53
積分:15
註冊:2007-06-12

發送簡訊給我
#16 引用回覆 回覆 發表時間:2008-07-24 10:49:41 IP:59.125.xxx.xxx 訂閱
看了各位先進的討論,讓我對Thread有點明白又多了許多要瞭解的地方。但是Thread的說明資料真是不易看懂,好不容易看到各位先進提供的CODE,想load來好好的try及請益,但檔案卻下載不下來,請問版大可否重新連結一份,便於大家下載使用,感謝!
===================引 用 h@visli 文 章===================
看來是版本不同,原代碼有變更了。謝謝Jow、dlleewameng
系統時間:2024-04-19 19:09:28
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!