canvas教程

fd详细教程|Excel_VBA_详细教程(4)

字号+ 作者:H5之家 来源:H5之家 2017-05-02 18:01 我要评论( )

给草草退出了, 这些资源将得不到释放而导致内存泄露. 尽管 Delphi 提供了 EndThread(其内部调用 ExitThread), 这也不需要我们手动操作(假如非要手动操作也是件很麻烦的事情, 因为很多时候你不知道线程是什么时候执

给草草退出了, 这些资源将得不到释放而导致内存泄露. 尽管 Delphi 提供了 EndThread(其内部调用 ExitThread), 这也不需要我们手动操作(假如非要手动操作也是件很麻烦的事情, 因为很多时候你不知道线程是什么时候执行完毕的).

除了 CreateThread, 还有一个 CreateRemoteThread, 可在其他进程中建立线程, 这不应该是现在学习的重点;

现在先集中精力把 CreateThread 的参数搞彻底.

倒着来吧, 先谈谈 CreateThread 将要返回的 "线程句柄".

"句柄" 类似指针, 但通过指针可读写对象, 通过句柄只是使用对象;

有句柄的对象一般都是系统级别的对象(或叫内核对象); 之所以给我们的是句柄而不是指针, 目的只有一个: "安全";

貌似通过句柄能做很多事情, 但一般把句柄提交到某个函数(一般是系统函数)后, 我们也就到此为止很难了解更多了; 事实上是系统并不相信我们.

不管是指针还是句柄, 都不过是内存中的一小块数据(一般用结构描述), 微软并没有公开句柄的结构细节, 猜一下它应该包括: 真实的指针地址、访问权限设置、引用计数等等.

既然 CreateThread 可以返回一个句柄, 说明线程属于 "内核对象".

实际上不管线程属于哪个进程, 它们在系统的怀抱中是平等的; 在优先级(后面详谈)相同的情况下, 系统会在相同的时间间隔内来运行一下每个线程, 不过这个间隔很小很小, 以至于让我们误以为程序是在不间断地运行.

这时你应该有一个疑问: 系统在去执行其他线程的时候, 是怎么记住前一个线程的数据状态的?

有这样一个结构 TContext, 它基本上是一个 CPU 寄存器的集合, 线程是数据就是通过这个结构切换的, 我们也可以通过 GetThreadContext 函数读取寄存器看看.

附上这个结构 TContext(或叫: CONTEXT、_CONTEXT) 的定义:

delphi多线程 delphi线程详细教程简单到复杂

PContext = ^TContext;

_CONTEXT = record

ContextFlags: DWORD; Dr0: DWORD;

Dr1: DWORD;

Dr2: DWORD;

Dr3: DWORD;

Dr6: DWORD;

Dr7: DWORD;

FloatSave: TFloatingSaveArea; SegGs: DWORD;

SegFs: DWORD;

SegEs: DWORD;

SegDs: DWORD;

Edi: DWORD;

Esi: DWORD;

Ebx: DWORD;

Edx: DWORD;

Ecx: DWORD;

Eax: DWORD;

Ebp: DWORD;

Eip: DWORD;

SegCs: DWORD;

EFlags: DWORD;

Esp: DWORD;

SegSs: DWORD;

end;

delphi多线程 delphi线程详细教程简单到复杂

CreateThread 的最后一个参数是 "线程的 ID";

既然可以返回句柄, 为什么还要输出这个 ID? 现在我知道的是:

1、线程的 ID 是唯一的; 而句柄可能不只一个, 譬如可以用 GetCurrentThread 获取一个伪句柄、可以用 DuplicateHandle 复制一个句柄等等.

2、ID 比句柄更轻便.

在主线程中 GetCurrentThreadId、MainThreadID、MainInstance 获取的都是主线程的 ID. ㈡、启动选项

function CreateThread(

lpThreadAttributes: Pointer;

dwStackSize: DWORD;

lpStartAddress: TFNThreadStartRoutine;

lpParameter: Pointer;

dwCreationFlags: DWORD; {启动选项}

var lpThreadId: DWORD

): THandle; stdcall;

CreateThread 的倒数第二个参数 dwCreationFlags(启动选项) 有两个可选值:

0: 线程建立后立即执行入口函数;

CREATE_SUSPENDED: 线程建立后会挂起等待.

可用 ResumeThread 函数是恢复线程的运行; 可用 SuspendThread 再次挂起线程.

这两个函数的参数都是线程句柄, 返回值是执行前的挂起计数.

什么是挂起计数?

SuspendThread 会给这个数 +1; ResumeThread 会给这个数 -1; 但这个数最小是 0.

当这个数 = 0 时, 线程会运行; > 0 时会挂起.

如果被 SuspendThread 多次, 同样需要 ResumeThread 多次才能恢复线程的运行.

delphi多线程 delphi线程详细教程简单到复杂

在下面的例子中, 有新线程不断给一个全局变量赋随机值;

同时窗体上的 Timer 控件每隔 1/10 秒就把这个变量写在窗体标题;

在这个过程中演示了 ResumeThread、SuspendThread 两个函数.

//上面图片中演示的代码。[]

unit Unit1;

interface

uses

Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

Dialogs, StdCtrls, ExtCtrls;

type

TForm1 = class(TForm)

Button1: TButton;

Button2: TButton;

Button3: TButton;

Timer1: TTimer;

procedure Button1Click(Sender: TObject);

procedure Button2Click(Sender: TObject);

procedure Button3Click(Sender: TObject);

procedure FormCreate(Sender: TObject);

procedure

Timer1Timer(Sender: TObject);

delphi多线程 delphi线程详细教程简单到复杂

end;

var

Form1: TForm1;

implementation

{$R *.dfm}

var

hThread: THandle; {线程句柄}

num: Integer; {全局变量, 用于记录随机数}

{线程入口函数}

function MyThreadFun(p: Pointer): Integer; stdcall; begin

while True do {假如线程不挂起, 这个循环将一直循环下去} begin

num := Random(100);

end;

Result := 0;

end;

{建立并挂起线程}

procedure TForm1.Button1Click(Sender: TObject); var

ID: DWORD;

begin

delphi多线程 delphi线程详细教程简单到复杂

hThread := CreateThread(nil, 0, @MyThreadFun, nil, CREATE_SUSPENDED, ID);

Button1.Enabled := False;

end;

{唤醒并继续线程}

procedure TForm1.Button2Click(Sender: TObject);

begin

ResumeThread(hThread);

end;

{挂起线程}

procedure TForm1.Button3Click(Sender: TObject);

begin

SuspendThread(hThread);

end;

procedure TForm1.FormCreate(Sender: TObject);

begin

Timer1.Interval := 100;

end;

procedure TForm1.Timer1Timer(Sender: TObject);

begin

Text := IntToStr(num);

end;

end.

delphi多线程 delphi线程详细教程简单到复杂

㈢、入口函数的参数

function CreateThread(

lpThreadAttributes: Pointer;

dwStackSize: DWORD;

lpStartAddress: TFNThreadStartRoutine;

lpParameter: Pointer; {入口函数的参数}

dwCreationFlags: DWORD;

var lpThreadId: DWORD

): THandle; stdcall;

线程入口函数的参数是个无类型指针(Pointer), 用它可以指定任何数据; 本例是把鼠标点击窗体的坐标传递给线程的入口函数, 每次点击窗体都会创建一个线程.

运行效果图:

//上面演示的代码

unit Unit1;

interface

uses

Windows, Messages, SysUtils, Variants, Classes, Graphics, Control

s, Forms,

delphi多线程 delphi线程详细教程简单到复杂

Dialogs;

type

TForm1 = class(TForm)

procedure FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);

end;

var

Form1: TForm1;

implementation

{$R *.dfm}

var

pt: TPoint; {这个坐标点将会已指针的方式传递给线程, 它应该是全局的}

function MyThreadFun(p: Pointer): Integer; stdcall;

var

i: Integer;

pt2: TPoint; {因为指针参数给的点随时都在变, 需用线程的局部变量存起来}

begin

pt2 := PPoint(p)^; {转换}

for i := 0 to 1000000 do

begin

with Form1.Canvas do begin

delphi多线程 delphi线程详细教程简单到复杂

Lock;

TextOut(pt2.X, pt2.Y, IntToStr(i));

Unlock;

end;

end;

Result := 0;

end;

procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);

var

ID: DWORD;

begin

pt := Point(X, Y);

CreateThread(nil, 0, @MyThreadFun, @pt, 0, ID);

{下面这种写法更好理解, 其实不必, 因为 PPoint 会自动转换为 Pointer 的}

//CreateThread(nil, 0, @MyThreadFun, Pointer(@pt), 0, ID); end;

end.

这个例子还有不严谨的地方: 当一个线程 Lock 窗体的 Canvas 时, 其他线程在等待; 线程在等待时, 其中的计数也还在增加. 这也就是说: 现在并没有去处理线程的同步; 同步是多线程中最重要的课题, 快到了.

另外有个小技巧: 线程函数的参数是个 32 位(4个字节)的指针, 仅就本例来讲, 可以让它的 "高16位" 和 "低16位" 分别携带 X 和 Y; 这样就不需要哪个全局的 pt 变量了.

delphi多线程 delphi线程详细教程简单到复杂

其实在 Windows 的消息中就是这样传递坐标的, 在 Windows 的消息中一般高字节是 Y、低字节是 X; 咱们这么来吧, 这样还可以使用给消息准备的一些方便的函数.

重写本例代码(当然运行效果和窗体文件都是一样的):

unit Unit1;

interface

uses

Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

Dialogs;

type

TForm1 = class(TForm)

procedure FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);

end;

var

Form1: TForm1;

implementation

{$R *.dfm}

function MyThreadFun(p: Pointer): Integer; stdcall;

var

delphi多线程 delphi线程详细教程简单到复杂

i: Integer;

x,y: Word;

begin

x := LoWord(Integer(p));

y := HiWord(Integer(p));

{如果不使用 LoWord、HiWord 函数可以像下面这样: }

//x := Integer(p);

//y := Integer(p) shr 16;

for i := 0 to 1000000 do

begin

with Form1.Canvas do begin

Lock;

TextOut(x, y, IntToStr(i));

Unlock;

end;

end;

Result := 0;

end;

procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);

var

ID: DWORD;

num: Integer;

begin

num := MakeLong(X, Y);

{如果不使用 MekeLong、MakeWParam、MakeLParam、MakeResult 等函数, 可以像下面这样: }

delphi多线程 delphi线程详细教程简单到复杂

//num := Y shl 16 + X;

CreateThread(nil, 0, @MyThreadFun, Ptr(num), 0, ID);

{上面的 Ptr 是专门将一个数字转换为指针的函数, 当然也可以这样: } //CreateThread(nil, 0, @MyThreadFun, Pointer(num), 0, ID); end;

end.

㈣、入口函数的指针

function CreateThread(

lpThreadAttributes: Pointer;

dwStackSize: DWORD;

lpStartAddress: TFNThreadStartRoutine; {入口函数的指针}

lpParameter: Pointer;

dwCreationFlags: DWORD;

var lpThreadId: DWORD

): THandle; stdcall;

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • delphi开发经验技巧...

    delphi开发经验技巧...

    2016-12-24 14:02

网友点评