Delphi5學習筆記之三

uuxa發表於2007-03-01

126. 在使用Printer.Abort();終止列印後,不能再呼叫Printer.CanvasPrinter.EndDoc來對印表機進行列印操作。

127.使用Printer物件列印的詳細步驟:

一、確認要列印的物件。

二、確認印表機畫布的測量單位。

三、確認每個要插入到目標上的物件的測量單位。

四、確定一個在整個列印中使用的公共測量單位,通常是印表機的測量單位,即畫素。

五、把其它各種物件的測量單位轉換成公共測量單位。

六、用公共測量單位計算出列印物件的尺寸。在Pascal裡,要列印的尺寸由TPoint結構來表示。

七、根據步驟六得到列印物件的尺寸及印表機畫布的座標系統來計算將要輸出到畫布的各個物件的位置。在Pascal中,使用TRect來表示物件的位置。

八、利用前面幾步得到的資料寫出列印函式。

[@more@]

128.獲取印表機資訊,例如印表機是否可以分段列印(分段列印:它是一種可以在一定記憶體限制下提高列印速度和降低硬碟空間的技術)。可以呼叫API函式來獲得該資訊。

129.透過呼叫API函式GetDeviceCaps()可以獲得諸如印表機、繪圖儀、顯示器等裝置的資訊。還有一點需要清楚,在呼叫一個印表機不支援的功能時,印表機仍然可以正常工作。例如呼叫GetDeviceCaps得知印表機不支援BitBlt函式或TrueType字型,但是可以呼叫這些函式,GDI會模擬這些函式。

130.判斷印表機是否支援雙面列印方法:

DeviceCapablitiesA(Device,Port,DC_DVPLEX,nil,nil);//該函式在Winspool.pas中定義

注:函式DeviceCapablities專門用於印表機。

131.執行緒:執行緒是一種系統物件,它表示在程式中的一條執行路徑,一個Win32應用程式至少要有一個執行緒,它被稱為主執行緒或預設執行緒。使用執行緒我認為主要的目的就是利用執行緒使不同的程式碼可以同時執行(這裡所講的同時執行實際上只有在多個CPU時才能實現,所謂的同時只是作業系統給人的一種感覺,因為系統把CPU的時間分成了很多時間片,而每一個時間片又非常短,這樣在執行每一個時間片時,人是感覺不到系統是在多個執行緒間來回切換分配CPU給這些執行緒的)

132.說一下作業系統,Win32是一種“搶佔式的作業系統”,而Win3.1是“協作式多工環境”。Win32的搶佔式作業系統是指:所有的執行緒都由作業系統負責在什麼時候執行。這也就是說,就算某個執行緒進入了死鎖狀態,作業系統也會讓其它執行緒繼續執行。而在Win3.1中,所謂的“協作式多工環境”是指什麼呢?它是指:所有的應用程式必須能夠確保在執行完後,把控制權交給作業系統。這也就是說,如果在Win3.1中如果有一個應用程式進入了死鎖狀態,那麼整個作業系統將無法再將CPU分配給其它程式。16位的Windows依賴於應用程式的執行情況,並且不允許應用程式進入死迴圈、無窮遞迴、以及任何封閉狀態。

133.DelphiVCL是非執行緒安全的,也就是說在同一時間VCL只能有一個執行緒訪問。有少數是執行緒安全的,如:TCanvas類是執行緒安全的。(VCL的屬性流機制是執行緒安全的)

執行緒安全:我認為就是一個東西可以被多執行緒安全的讀寫。

134.Delphi中使用多執行緒的缺點就是不易於除錯。

135.Delphi把幾乎所有關於執行緒的API函式都封裝在TThread類中。不過關於執行緒同步的一些API沒有全部被封裝。注TThreadClasses.pas單元中定義。TThread類是抽象類,它的方法Execute ()是抽象的,在TThread的子類中必須實現方法Execute ()。注意在建立執行緒子類的物件時,如果像下面這樣,則Execute方法會在Create時執行,

SomeThread := TSomeThread.Create(False);//傳遞FalseCreate那麼在建立的同時會執行Execute方法。

雖然說傳遞False給執行緒的Create會使Execute方法自動執行,但是,如果你想操作Execute執行完後的結果,就要在Create建立完執行緒後呼叫Sleep(0)來使主執行緒暫停,如下所示:

SomeThread := TSomeThread.Create(False);

Sleep(0);

操作Execute執行的結果

……

因為只有呼叫了Sleep(0)後,主執行緒才能掛起,系統才能把CPU時間片分給執行緒SomeThread,讓SomeThread來執行它的方法Execute,否則Execute可能會在你想要呼叫時它的結果時而得不到。

注:一般情況下,在建立完執行緒物件後,還需要設定一下執行緒的其它屬性,這時最好在建立時傳遞TrueCreate。之後呼叫Resume來喚醒執行緒。

再說一下Execute方法:線上程執行完Execute方法後,我們就認為執行緒到此終止了,這時它會呼叫Delphi的一個標準函式EndThread(),這個函式再呼叫標準API函式ExitThread(),再由ExitThread來清除執行緒所佔用的棧。一般在Execute方法中設定執行緒的FreeOnTerminate屬性,這樣線上程終止時就會觸發OnTerminate事件(注意:只有設定了執行緒的FreeOnTerminate屬性後,才會線上程終止時觸發事件OnTerminate,屬性FreeOnTerminateExecute方法退出前設定就行),就有機會在事件OnTerminate裡清除執行緒物件了。

136.Win32中執行緒儲存在棧中。

137. 注意某些緊急情況下,可以使用Win32 API函式TerminateThread()來終止一個執行緒。但是,除非沒有別的辦法了,否則不要用它。例如,當執行緒程式碼陷入死迴圈時。

TerminateThread()的宣告如下:

function TerminateThread(hThread: THandle;dwExitCode: DWORD);

TThread的Handle屬性可以作為第一個引數,因此,TerminateThread()常這樣呼叫:TerminateThread(MyThread.Handle,0)

如果選擇使用這個函式,應該考慮到它的負面影響。首先,此函式在Windows NT與在

Windows 95/98下並不相同。在Windows 95/98 下,這個函式能夠自動清除執行緒所佔用的棧;而在Windows NT下,在程式被終止前棧仍然保留。其次,無論執行緒程式碼中是否有try... finally塊,這個函式都會使執行緒立即停止執行。這意味著,被執行緒開啟的檔案沒有被關閉、由執行緒申請的記憶體沒有被釋放等情況。而且,這個函式在終止執行緒的時候也不通知DLL,當DLL關閉時,這也容易出現問題。

138.執行緒中的Synchronize方法用於在主執行緒中執行一個方法,這個方法是Synchronize的引數,該引數是一個無引數的過程,如下所示:

procedure TCustomThread.Execute;

var

I: integer;

begin

FreeOnTerminate := True;(*設定FreeOnTerminate,

在執行完Execute後就會觸發OnTerminate事件,

這樣就有機會在OnTerminate事件裡清除執行緒物件了

*)

if not Terminated then begin

for i := 0 to 200000 do begin

Inc(GI,Round(abs(Sqrt(i))));

//SendMessage(MainForm.edtGetThreadValue.Handle,WM_SETTEXT,0,Integer(PChar(GI)));//使用訊息同步

Synchronize(GetValue);(*使用Synchronize實現同步,執行緒的方法Synchronize需要傳遞一個無引數的過程做為它的引數,這樣在主執行緒中就會呼叫GetValue過程*)

end;

end;

end;

procedure TCustomThread.GetValue;

begin

MainForm.edtGetThreadValue.Text := IntToStr(GI);

end;

//在實現這段程式碼過程中我發現,使用訊息同步執行緒要比使用Synchronize同步執行緒要慢很多。

139.使用函式GetThreadTimes來得到關於執行緒時間,

function GetThreadTimes(hThread: THandle;var lpCreationTime,lpExitTime,

lpKernelTime,lpUserTime: TFileTime):Boolean;

引數說明:

hThread是執行緒的控制程式碼。

LpCreationTime是執行緒建立的時間。

LpExitTime是執行緒退出的時間。

LpKernelTime是執行系統程式碼的時間。

LpUserTime是執行應用程式本身程式碼的時間。

這四個時間都是TfileTime型別的,該型別定義在Windows.pas單元中,

type

TfileTime = record

dwLowDatetime: DWORD;

dwHighDateTime: DWORD;

End;

由dwLowDatetime和dwHighDatetime可以組成64位的時間,它是開始於1601年1月1日以千萬分之一秒為單位的計數。

下面兩個函式用於把TfileTime型別的時間與Tdatetime型別時間進行相互轉換,

function FileTimeToDateTime(FileTime: TFileTime): TdateTime;

var

SysTime: TSystemtime;

begin

if not FileTimeToSystemtime(FileTime,SysTime) then

raise EconvertError.CreateFmt(‘FileTime To System Time Failed, and’+ ‘Error Code is %d’,[GetLastError]);

with SysTime do begin

Result := EncodeDate(wYear,wMonth,wDay) +

EncodeTime(wHour,wMinute,wSecond,wMillisecond);

end;

end;

function DateTimeToFileTime(DateTime: TDateTime): TFileTime;

var

SysTime: TSystemTime;

begin

with SysTime do begin

EncodeDate(wYear,wMonth,wDay);

EncodeTime(wHour,wMinute,wSecond,wMillisecond);

WDayOfWeek := DayOfWeek(DateTime);

end;

if not SystemTimeToFileTime(SysTime,Result) then

raise EConvertError.CreateFmt(‘System Time To FileTime Failed, and’+ ‘Error Code is %d’,[GetLastError]);

end;

注:GetThreadTimes不適用於Windows95/98,在這兩個作業系統中呼叫該函式將只返回False。

140.以前說過Sleep過程,現在再多說點,

procedure Sleep(dwMilliseconds:DWORD); stdcall;

Sleep()過程用來告訴作業系統,當前的執行緒在引數dwMilliseconds指定的時間內不需要分配任何CPU時間。通常,可以把引數dwMilliseconds設為0。儘管,這並沒有使當前的執行緒真的“睡眠”,但它使作業系統把CPU時間分給了其他優先順序相等或更高的執行緒。

141.執行緒的區域性儲存:首先說明一下為什麼要使用執行緒區域性儲存?這是防止執行緒間同一時間寫同一資料(或是操作同一個任何東西)而造成資料丟失的手段,例如:兩個執行緒都同時寫一個全域性變數就會導制意想不到的結果。所謂的執行緒區域性儲存是指屬於每個執行緒私有的資料儲存方式。實現執行緒區域性儲存方式有:

第一種:區域性變數,由於每一個執行緒都儲存在自己的棧中,各個執行緒都有一套區域性變數的副本,這樣執行緒間就不會相互干擾。

第二種:執行緒物件,將資料儲存線上程物件中,舉例說明,

type

TSomeThread = Class(TThread)

Private

//把變數SomeIntSomeStr儲存線上程TSomeThread

FSomeInt: integer;

FSomeStr: string;

……

End;

注:據說訪問執行緒物件中的資料要比訪問執行緒的區域性變數要快10倍。

第三種:使用關鍵字threadvar來宣告變數。使用threadvar宣告的是一個執行緒的區域性變數,因此該變數不能在宣告時初始化。如果Synchronize的引數(也就是那個過程)使用threadvar宣告的變數,會出現一些奇怪現象。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7416120/viewspace-901168/,如需轉載,請註明出處,否則將追究法律責任。

相關文章