




已阅读5页,还剩15页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
delphi多线程编程1多线程编程(1) - 先入门再说。多线程应该是编程工作者的基础技能, 但这个基础对我来讲的确有点难(起码昨天以前是这样).开始本应该是一篇洋洋洒洒的文字, 不过我还是提倡先做起来, 在尝试中去理解.先试试这个:procedure TForm1.Button1Click(Sender: TObject);vari: Integer;beginfor i := 0 to 500000 dobeginCanvas.TextOut(10, 10, IntToStr(i);end;end;上面程序运行时, 我们的窗体基本是 死 的, 可以在你在程序运行期间拖动窗体试试.Delphi 为我们提供了一个简单的办法(Application.ProcessMessages)来解决这个问题:procedure TForm1.Button1Click(Sender: TObject);vari: Integer;beginfor i := 0 to 500000 dobeginCanvas.TextOut(10, 10, IntToStr(i);Application.ProcessMessages;end;end;这个 Application.ProcessMessages; 一般用在比较费时的循环中, 它会检查并先处理消息队列中的其他消息.但这算不上多线程, 譬如: 运行中你拖动窗体, 循环会暂停下来.在使用多线程以前, 让我们先简单修改一下程序:function MyFun: Integer;vari: Integer;beginfor i := 0 to 500000 dobeginForm1.Canvas.Lock;Form1.Canvas.TextOut(10, 10, IntToStr(i);Form1.Canvas.Unlock;end;Result := 0;end;procedure TForm1.Button1Click(Sender: TObject);beginMyFun;end;细数上面程序的变化:1、首先这还不是多线程的, 也会让窗体假 死 一会;2、把执行代码写在了一个函数里, 但这个函数不属于 TForm1 的方法, 所以使用 Canvas 是必须冠以名称(Form1);3、既然是个函数, (不管是否必要)都应该有返回值;4、使用了 500001 次 Lock 和 Unlock.Canvas.Lock 好比在说: Canvas(绘图表面)正忙着呢, 其他想用 Canvas 的等会;Canvas.Unlock : 用完了, 解锁!在 Canvas 中使用 Lock 和 Unlock 是个好习惯, 在不使用多线程的情况下这无所谓, 但保不准哪天程序会扩展为多线程的; 我们现在学习多线程, 当然应该用.在 Delphi 中使用多线程有两种方法: 调用 API、使用 TThread 类; 使用 API 的代码更简单.function MyFun(p: Pointer): Integer; stdcall;vari: Integer;beginfor i := 0 to 500000 dobeginForm1.Canvas.Lock;Form1.Canvas.TextOut(10, 10, IntToStr(i);Form1.Canvas.Unlock;end;Result := 0;end;procedure TForm1.Button1Click(Sender: TObject);varID: THandle;beginCreateThread(nil, 0, MyFun, nil, 0, ID);end;代码分析:CreateThread 一个线程后, 算上原来的主线程, 这样程序就有两个线程、是标准的多线程了;CreateThread 第三个参数是函数指针, 新线程建立后将立即执行该函数, 函数执行完毕, 系统将销毁此线程从而结束多线程的故事.CreateThread 要使用的函数是系统级别的, 不能是某个类(譬如: TForm1)的方法, 并且有严格的格式(参数、返回值)要求, 不管你暂时是不是需要都必须按格式来;因为是系统级调用, 还要缀上 stdcall, stdcall 是协调参数顺序的, 虽然这里只有一个参数没有顺序可言, 但这是使用系统函数的惯例.CreateThread 还需要一个 var 参数来接受新建线程的 ID, 尽管暂时没用, 但这也是格式; 其他参数以后再说吧.这样一个最简单的多线程程序就出来了, 咱们再用 TThread 类实现一次typeTMyThread = class(TThread)protectedprocedure Execute; override;end;procedure TMyThread.Execute;vari: Integer;beginFreeOnTerminate := True; 这可以让线程执行完毕后随即释放for i := 0 to 500000 dobeginForm1.Canvas.Lock;Form1.Canvas.TextOut(10, 10, IntToStr(i);Form1.Canvas.Unlock;end;end;procedure TForm1.Button1Click(Sender: TObject);beginTMyThread.Create(False);end;TThread 类有一个抽象方法(Execute), 因而是个抽象类, 抽象类只能继承使用, 上面是继承为 TMyThread.继承 TThread 主要就是实现抽象方法 Execute(把我们的代码写在里面), 等我们的 TMyThread 实例化后, 首先就会执行 Execute 方法中的代码.按常规我们一般这样去实例化:procedure TForm1.Button1Click(Sender: TObject);varMyThread: TMyThread;beginMyThread := TMyThread.Create(False);end;因为 MyThread 变量在这里毫无用处(并且编译器还有提示), 所以不如直接写做 TMyThread.Create(False);我们还可以轻松解决一个问题, 如果: TMyThread.Create(True) ?procedure TForm1.Button1Click(Sender: TObject);varMyThread: TMyThread;beginMyThread := TMyThread.Create(True);MyThread.Resume;end;/可简化为:procedure TForm1.Button1Click(Sender: TObject);beginwith TMyThread.Create(True) do Resume;end;使用 TThread 类时, Delphi 有提供的模板, 但用 IDE 写代码很方便, 我重写一遍录下来给大家看:多线程编程(1) - 先入门再说期间使用了 Ctrl+J、Shift+Ctrl+C、Ctrl+Alt+P 等快捷键.重要的修正与补充: 在 TThread 类的例子中, 应该有这句: FreeOnTerminate := True; (原来漏掉, 代码中已加上; 但动画加不上了).先说它什么意思:类 Create 了就要 Free;但 TThread(的子类) 有特殊性, 很多时候我们不能确定新建的线程什么时候执行完(也就是什么时候该释放);如果线程执行完毕自己知道释放就好了, 所以 TThread 给了一个布尔属性 FreeOnTerminate, 如果为 True, 线程执行完毕后就会自释放.我怎么会忽略了这么重要的问题呢? 原因有二:1、我一直在追求最精炼的代码;2、我手头上不只一本书上介绍说: FreeOnTerminate 的默认值是 True(错误!), 经落实, 应该是 False, 起码在 Delphi 2007 和 2009 中是这样; 或许以前的某个版本和现在不一样多线程编程(2) - 从 CreateThread 说起。function CreateThread(lpThreadAttributes: Pointer; 安全设置dwStackSize: DWORD;堆栈大小lpStartAddress: TFNThreadStartRoutine; 入口函数lpParameter: Pointer;函数参数dwCreationFlags: DWORD;启动选项var lpThreadId: DWORD输出线程 ID ): THandle; stdcall; 返回线程句柄在 Windows 上建立一个线程, 离不开 CreateThread 函数;TThread.Create 就是先调用了 BeginThread (Delphi 自定义的), BeginThread 又调用的 CreateThread.既然有建立, 就该有释放, CreateThread 对应的释放函数是: ExitThread, 譬如下面代码:procedure TForm1.Button1Click(Sender: TObject);beginExitThread(0); 此句即可退出当前程序, 但不建议这样使用end;代码注释:当前程序是一个进程, 进程只是一个工作环境, 线程是工作者;每个进程都会有一个启动线程(或叫主线程), 也就是说: 我们之前大量的编码都是写给这个主线程的;上面的 ExitThread(0); 就是退出这个主线程;系统不允许一个没有线程的进程存在, 所以程序就退出了.另外: ExitThread 函数的参数是一个退出码, 这个退出码是给之后的其他函数用的, 这里随便给个无符号整数即可.或许你会说: 这个 ExitThread 挺好用的; 其实不管是用 API 还是用 TThread 类写多线程, 我们很少用到它; 因为:1、假如直接使用 API 的 CreateThread, 它执行完入口函数后会自动退出, 无需 ExitThread;2、用 TThread 类建立的线程又绝不能使用 ExitThread 退出; 因为使用 TThread 建立线程时会同时分配更多资源(譬如你自定义的成员、还有它的祖先类(TObject)分配的资源等等), 如果用 ExitThread 给草草退出了, 这些资源将得不到释放而导致内存泄露. 尽管 Delphi 提供了 EndThread(其内部调用 ExitThread), 这也不需要我们手动操作(假如非要手动操作也是件很麻烦的事情, 因为很多时候你不知道线程是什么时候执行完毕的).除了 CreateThread, 还有一个 CreateRemoteThread, 可在其他进程中建立线程, 这不应该是现在学习的重点;现在先集中精力把 CreateThread 的参数搞彻底.倒着来吧, 先谈谈 CreateThread 将要返回的 线程句柄.句柄 类似指针, 但通过指针可读写对象, 通过句柄只是使用对象;有句柄的对象一般都是系统级别的对象(或叫内核对象); 之所以给我们的是句柄而不是指针, 目的只有一个: 安全;貌似通过句柄能做很多事情, 但一般把句柄提交到某个函数(一般是系统函数)后, 我们也就到此为止很难了解更多了; 事实上是系统并不相信我们.不管是指针还是句柄, 都不过是内存中的一小块数据(一般用结构描述), 微软并没有公开句柄的结构细节, 猜一下它应该包括: 真实的指针地址、访问权限设置、引用计数等等.既然 CreateThread 可以返回一个句柄, 说明线程属于 内核对象.实际上不管线程属于哪个进程, 它们在系统的怀抱中是平等的; 在优先级(后面详谈)相同的情况下, 系统会在相同的时间间隔内来运行一下每个线程, 不过这个间隔很小很小, 以至于让我们误以为程序是在不间断地运行.这时你应该有一个疑问: 系统在去执行其他线程的时候, 是怎么记住前一个线程的数据状态的?有这样一个结构 TContext, 它基本上是一个 CPU 寄存器的*, 线程是数据就是通过这个结构切换的, 我们也可以通过 GetThreadContext 函数读取寄存器看看.附上这个结构 TContext(或叫: CONTEXT、_CONTEXT) 的定义Context = TContext;_CONTEXT = recordContextFlags: 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;CreateThread 的最后一个参数是 线程的 ID;既然可以返回句柄, 为什么还要输出这个 ID? 现在我知道的是:1、线程的 ID 是唯一的; 而句柄可能不只一个, 譬如可以用 GetCurrentThread 获取一个伪句柄、可以用 DuplicateHandle 复制一个句柄等等.2、ID 比句柄更轻便.在主线程中 GetCurrentThreadId、MainThreadID、MainInstance 获取的都是主线程的 ID.啰哩啰嗦一大些, 才说了 CreateThread 一个参数, 下篇继续.线程的学习还在入门中, 把我的理解写在这, 最期望的收获是得到指正.多线程编程(3) - 从 CreateThread 说起续。 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 多次才能恢复线程的运行.在下面的例子中, 有新线程不断给一个全局变量赋随机值;同时窗体上的 Timer 控件每隔 1/10 秒就把这个变量写在窗体标题;在这个过程中演示了 ResumeThread、SuspendThread 两个函数.运行效果图:代码文件:unit Unit1;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, StdCtrls, ExtCtrls;typeTForm1 = 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 Timer
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
评论
0/150
提交评论