频道分类

DELPHI 多线程(API实现)

作者:admin 来源: 日期:2020/3/1 15:36:20 人气: 标签:

 
首先看下构造函数:(会自动销毁)

function CreateThread(

  lpThreadAttributes: Pointer;           {安全设置}   {一般为Nil}

  dwStackSize: DWORD;                    {堆栈大小} {0为默认大小}

  lpStartAddress: TFNThreadStartRoutine; {入口函数} { 例:@MyFun}

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

  dwCreationFlags: DWORD;                {启动选项}  {有两个值,0时立即执行入口函数,CREATE_SUSPENDED,挂起等待。可用 ResumeThread(句柄) 函数是恢复线程的运行; 可用 SuspendThread(句柄) 再次挂起线程.}

  var lpThreadId: DWORD                  {输出线程 ID } {输入你的接收句柄变量}

): THandle; stdcall;                     {返回线程句柄}

例子:
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    btn1: TButton;
    btn2: TButton;
    procedure btn1Click(Sender: TObject);
    procedure btn2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function MyFun(p:Pointer):integer;stdcall; {工作线程调入函数,stdcall用于多个线程排序以及系统级别调用加此关键字}
var
  i:integer;
begin
  for i := 0 to 500000 do    
  begin
    with Form1.Canvas do
    begin
      Lock;
      TextOut(50,10,IntToStr(i)); {50和10是坐标X和Y}
      Unlock;
      Application.ProcessMessages;
    end;
  end;
end;

procedure TForm1.btn1Click(Sender: TObject);{主线程}
var
  i:integer;
begin
  for i := 0 to 500000 do  
  begin
    with Form1.Canvas do
    begin
      Lock;
      TextOut(10,10,IntToStr(i)); {10和10是坐标X和Y}
      Unlock;
      Application.ProcessMessages;{加上去才在计数时不会卡住,拖动窗体时,计数会有停顿}
    end;
  end;

end;

procedure TForm1.btn2Click(Sender: TObject);{工作线程,拖动窗口时计数不会停顿,因为和主线程分开工作了}
var
  ID:THandle; {用于接收线程返回句柄,也可以用DWORD}
begin
  CreateThread(nil,0,@MyFun,nil,0,ID);  {API创建线程}
end;

end.

CriticalSection(临界区):
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    lst1: TListBox;
    btn1: TButton;
    procedure btn1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  CS:TRTLCriticalSection; {声明临界}

function MyFun(p:Pointer):integer;stdcall;
var
  i:integer;
begin
  EnterCriticalSection(CS);  {我要用了,别人先别用}
  for i := 0 to 100 - 1 do
  begin
    Form1.lst1.Items.Add(IntToStr(i));
  end;
  LeaveCriticalSection(CS);  {我用完了,别可以用了}

end;

procedure TForm1.btn1Click(Sender: TObject);
var
  ID:THandle;
begin
  InitializeCriticalSection(CS); {初始化临界}
  CreateThread(nil,0,@MyFun,nil,0,ID);
  CreateThread(nil,0,@MyFun,nil,0,ID);
  CreateThread(nil,0,@MyFun,nil,0,ID);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  DeleteCriticalSection(CS);  {删除临界}
end;

end.

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

先说明等待函数(后面要配套使用):

function WaitForSingleObject(
hHandle: THandle; {要等待的对象句柄}
dwMilliseconds: DWORD {等待的时间, 单位是毫秒}
): DWORD; stdcall; {返回值如下:}

WAIT_OBJECT_0 {等着了, 本例中是: 等的那个进程终于结束了}
WAIT_TIMEOUT {等过了点(你指定的时间), 也没等着}
WAIT_ABANDONED {好不容易等着了, 但人家还是不让咱执行; 这一般是互斥对象}

//WaitForSingleObject 的第二个参数一般给常数值 INFINITE, 表示一直等下去, 死等.

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Mutex (互斥对象)

要理解的函数有:

function CreateMutex(
lpMutexAttributes: PSecurityAttributes; {安全参数,默认真nil}
bInitialOwner: BOOL; {是否让创建者(此例中是主线程)拥有该互斥对象}{一般为False}
lpName: PWideChar {可以给此互斥对象取个名字, 如果不要名字可赋值为 nil}
): THandle;

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    btn1: TButton;
    procedure btn1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  hMutex:THandle; {声明互斥变量句柄}
  f:Integer;      {用于协调输出位置的变量}

function MyFun(p:Pointer):Integer;stdcall;
var
  i,y:integer;
begin
  Inc(f);  {步进f}
  y:=20*f;
  if WaitForSingleObject(hMutex,INFINITE)=WAIT_OBJECT_0 then   {等待函数}
  begin
    for i := 0 to 500 do
    begin
      with Form1.Canvas do
      begin
        Lock;
        TextOut(10,Y,IntToStr(i));
        Unlock;
        sleep(1); {太快怕忙不过来}
      end;
    end;
    ReleaseMutex(hMutex);
  end;
end;  


procedure TForm1.btn1Click(Sender: TObject);
var
  ID:THandle;
begin
  f:=0; {初始化f为0}
  Repaint; {重画}
  CloseHandle(hMutex); {先关闭句柄}
  hMutex:=CreateMutex(nil,False,nil);  {创建互斥体}
  CreateThread(nil,0,@MyFun,nil,0,ID);
  CreateThread(nil,0,@MyFun,nil,0,ID);
  CreateThread(nil,0,@MyFun,nil,0,ID);
  CreateThread(nil,0,@MyFun,nil,0,ID);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  CloseHandle(hMutex);  {关闭句柄}
end;

end.

Semaphore(信号或叫信号量)

要理解的函数:

CreateSemaphore(安全设置, 初始信号数, 信号总数, 信号名称) 建立信号对象;
参数四: 和 Mutex 一样, 它可以有个名称, 也可以没有, 本例就没有要名称(nil); 有名称的一般用于跨进程.
参数三: 信号总数, 是 Semaphore 最大处理能力, 就像银行一共有多少个业务窗口一样; 
参数二: 初始信号数, 这就像银行的业务窗口很多, 但打开了几个可不一定, 如果没打开和没有一样;{本例用个EDIT输入数量,每次释放后又进行同样数量}
参数一: 安全设置和前面一样, 使用默认(nil)即可.

ReleaseSemaphore(接受信号量句柄,1[接收多少个信号] , nil[一般为空,如果是指针可以接受到此时共闲置了多少个信号量]);

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    btn1: TButton;
    edt1: TEdit;
    procedure btn1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btn1KeyPress(Sender: TObject; var Key: Char);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  hsmaphore:THandle; {信号量句柄}
  f:Integer;         {协调输出的变量}

function MyFun(p:Pointer):integer;
var
  i,y:integer;
begin
  Inc(f);
  y:=20*f;
  if WaitForSingleObject(hsmaphore,INFINITE)=WAIT_OBJECT_0 then
  begin
    for i := 0 to 500 do
    begin
      with Form1,Canvas do
      begin
        Lock;
        TextOut(10,y,IntToStr(i));
        Unlock;
        Sleep(1);
      end;
    end;
    ReleaseSemaphore(hsmaphore,1,nil); {释放函数}
  end;
  Result:=0;
end;

procedure TForm1.btn1Click(Sender: TObject);
var
  ID:DWORD;
begin
  CloseHandle(hsmaphore);  {先关闭句柄}
  hsmaphore:=CreateSemaphore(nil,StrToInt(edt1.Text),5,nil); {创建句柄}
  CreateThread(nil,0,@MyFun,nil,0,ID);   {创建线程}
  CreateThread(nil,0,@MyFun,nil,0,ID);
  CreateThread(nil,0,@MyFun,nil,0,ID);
  CreateThread(nil,0,@MyFun,nil,0,ID);
  CreateThread(nil,0,@MyFun,nil,0,ID);
end;

procedure TForm1.btn1KeyPress(Sender: TObject; var Key: Char);
begin
  if not (Key in ['1'..'5']) then Key:=#0;  {设置只能输入1到5,并且在控件属性设置宽度为1}
  
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  CloseHandle(hsmaphore);  {关闭句柄}
end;

end.

Event (事件对象)

function CreateEvent(
lpEventAttributes: PSecurityAttributes; {安全设置}
bManualReset: BOOL; {第一个布尔}
bInitialState: BOOL; {第二个布尔}
lpName: PWideChar {对象名称}
): THandle; stdcall; {返回对象句柄}

//第一个布尔为 False 时, 事件对象控制一次后将立即重置(暂停); 为 True 时可手动暂停.
//第二个布尔为 False 时, 对象建立后控制为暂停状态; True 是可运行状态.

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    btn1: TButton;
    btn2: TButton;
    btn3: TButton;
    btn4: TButton;
    btn5: TButton;
    procedure btn1Click(Sender: TObject);
    procedure btn2Click(Sender: TObject);
    procedure btn3Click(Sender: TObject);
    procedure btn4Click(Sender: TObject);
    procedure btn5Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  hEvent:THandle;
  f:integer;

function MyFun (p:Pointer):Integer;
var
  i,y:integer;
begin
  Inc(f);
  y:=20*f;
  for i := 0 to 200000 do
  begin
    if WaitForSingleObject(hEvent,INFINITE)=WAIT_OBJECT_0 then
    begin
      Form1.Canvas.Lock;
      Form1.Canvas.TextOut(10,y,IntToStr(i));
      Form1.Canvas.Unlock;
      
    end;
  end;
  Result:=0;
end;

procedure TForm1.btn1Click(Sender: TObject);
var
  ID:DWORD;
begin
  Repaint;  {重画}
  f:=0;
  CloseHandle(hEvent);{先关闭线程}
  hEvent:=CreateEvent(nil,True,True,nil)  {创建事件}
end;

procedure TForm1.btn2Click(Sender: TObject);
var
  ID:DWORD;
begin
  CreateThread(nil,0,@MyFun,nil,0,ID);  {创建线程}

end;

procedure TForm1.btn3Click(Sender: TObject);
begin
  ResetEvent(hEvent); {暂停,可对当前所有事件相关线程暂停}
end;

procedure TForm1.btn4Click(Sender: TObject);
begin
  SetEvent(hEvent);  {启动,可对当前所有事件相关线程启动}
end;

procedure TForm1.btn5Click(Sender: TObject);
begin
  PulseEvent(hEvent); {启动一次再暂停,可对当前所有事件相关线程}
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  btn1.Caption := '创建 Event 对象';
  btn2.Caption := '创建线程';
  btn3.Caption := 'ResetEvent';
  btn4.Caption := 'SetEvent';
  btn5.Caption := 'PulseEvent';
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  CloseHandle(hEvent); {关闭事件句柄}
end;

end.

等待记时器对象:WaitableTimer{比较复杂,可不记,需要使用时查阅}

{它的主要功用类似 TTimer 类,既然有了方便的 TTimer, 何必再使用 WaitableTimer 呢?
因为 WaitableTimer 比 TTimer 精确的多, 它的间隔时间可以精确到毫秒、它的指定时间甚至是精确到 0.1 毫秒;
而 TTimer 驱动的 WM_TIMER 消息, 是消息队列中优先级最低的, 也就是再同一时刻 WM_TIMER 消息总是被最后处理.
还有重要的一点 WaitableTimer 可以跨线程、跨进程使用.}

需要了解的函数:

function CreateWaitableTimer(
lpTimerAttributes: PSecurityAttributes; {安全}
bManualReset: BOOL; {True: 可调度多个线程; False: 只调度一个线程}
lpTimerName: PWideChar {名称}
): THandle; stdcall; {返回句柄}

function SetWaitableTimer(
hTimer: THandle; {句柄} {WaitableTimer 对象的句柄}
var lpDueTime: TLargeInteger; {起始时间} //0为马上,另有相对时间如:-3*10000000; {3秒钟后执行},绝对时间:如:'2016-08-26 10:06:00' 需要转换
lPeriod: Longint; {间隔时间}
pfnCompletionRoutine: TFNTimerAPCRoutine;{回调函数的指针,不用时为空} 
lpArgToCompletionRoutine: Pointer; {给回调函数的参数,不用时为空}
fResume: BOOL {是否唤醒系统}{此值若是 True, 即使系统在屏保或待机状态, 时间一到线程和系统将都被唤醒!}
): BOOL; stdcall; {}

 

例1:指定多少秒后运行(相对时间):

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    btn1: TButton;
    procedure btn1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  hWaitableTimer:THandle;
  f:integer;

function MyFun(p:Pointer):integer;
var
  i,y:integer;
begin
  inc(f);
  y:=20*f;

  if WaitForSingleObject(hWaitableTimer,INFINITE)=WAIT_OBJECT_0 then
  begin
    for I := 0 to 1000 do
    begin
      Form1.Canvas.Lock;
      Form1.Canvas.TextOut(10,Y,IntToStr(I));
      Form1.Canvas.Unlock;
      Sleep(1);
    end;
  end;
  Result:=0;
end;



procedure TForm1.btn1Click(Sender: TObject);
var
  DueTimer:Int64;
  ID:DWORD;
begin
  hWaitableTimer:=CreateWaitableTimer(nil,True,nil); {创建等待计时器,允许多线程同时进行}
  DueTimer:=-3*10000000; {三秒后执行}
  SetWaitableTimer(hWaitableTimer,DueTimer,0,nil,nil,False);  {设置计时器开始运行时间}

  Repaint;
  f:=0;
  CreateThread(nil,0,@MyFun,nil,0,ID);
  CreateThread(nil,0,@MyFun,nil,0,ID);
  CreateThread(nil,0,@MyFun,nil,0,ID);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  CloseHandle(hWaitableTimer); {句柄}
end;

end.

例2:指定一个时间里运行(绝对时间):

//StrToDateTime -> DateTimeToSystemTime -> SystemTimeToFileTime -> LocalFileTimeToFileTime 时间转换

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    btn1: TButton;
    procedure btn1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  hWaitableTimer:THandle;
  f:integer;

function MyFun(p:Pointer):integer;
var
  i,y:integer;
begin
  inc(f);
  y:=20*f;

  if WaitForSingleObject(hWaitableTimer,INFINITE)=WAIT_OBJECT_0 then
  begin
    for I := 0 to 1000 do
    begin
      Form1.Canvas.Lock;
      Form1.Canvas.TextOut(10,Y,IntToStr(I));
      Form1.Canvas.Unlock;
      Sleep(1);
    end;
  end;
  Result:=0;
end;



procedure TForm1.btn1Click(Sender: TObject);
const
  strTime='2016-8-29 14:41:30';
var
  DueTimer:Int64;
  ID:DWORD;
  st:TSystemTime;
  ft,Utc:TFileTime;
  dt:TDateTime;
begin
  DateTimeToSystemTime(StrToDateTime(strTime), st); {从 TDateTime 到 TSystemTime}
  SystemTimeToFileTime(st, ft);                     {从 TSystemTime 到 TFileTime}
  LocalFileTimeToFileTime(ft, UTC);                 {从本地时间到国际标准时间 UTC}
  DueTimer:= Int64(UTC);                            {函数需要的是 Int64}

  hWaitableTimer:=CreateWaitableTimer(nil,True,nil); {创建等待计时器,允许多线程同时进行}
  SetWaitableTimer(hWaitableTimer,DueTimer,0,nil,nil,False);  {设置计时器开始运行时间}

  Repaint;
  f:=0;
  CreateThread(nil,0,@MyFun,nil,0,ID);
  CreateThread(nil,0,@MyFun,nil,0,ID);
  CreateThread(nil,0,@MyFun,nil,0,ID);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  CloseHandle(hWaitableTimer); {关闭句柄}
end;

end.

下面例子需要了解以下函数:

function SleepEx(
dwMilliseconds: DWORD; {毫秒数} {INFINITE 表示一直等}
bAlertable: BOOL {布尔值}
): DWORD; stdcall;

//第一个参数和 Sleep 的那个参数是一样的, 是线程等待(或叫挂起)的时间, 时间一到不管后面参数如何都会返回.

//第二个参数如果是 False, SleepEx 将不会关照 APC 函数是否入列;
//若是 True, 只要有 APC 函数申请, SleepEx 不管第一个参数如何都会把 APC 推入队列并随 APC 函数一起返回.

//注意: SetWaitableTimer 和 SleepEx 必须在同一个线程才可以.

procedure TimerAPCProc(lpArgToCompletionRoutine: Pointer;dwTimerLowValue: DWORD;dwTimerHighValue: DWORD); stdcall;
//系统定义给SetWaitableTimer第一个回调函数指针的格式函数{名字可以变,格式和类型不能变。}

 

例3:窗口标题自增数字

本例在SetWaitableTimer使用TimerAPCProc回调函数,但不使用回调函数的参数

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    btn1: TButton;
    btn2: TButton;
    procedure btn1Click(Sender: TObject);
    procedure btn2Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  hTimer:THandle;

procedure TimerAPCProc(APointer:Pointer;dwTimerLowValue:DWORD;dwTimerHighValue:DWORD);stdcall;
begin
  Form1.Text:=IntToStr(StrToIntDef(Form1.Text,0)+1);
  SleepEx(INFINITE,True);     {在回调参数里加这一句,会不断的循环}
end;

function MyFun(p:Pointer):integer;stdcall;
var
  DueTime:Int64;
begin
  DueTime:=0;
  {SetWaitableTimer 必须与 SleepEx 在同一线程}
  if SetWaitableTimer(hTimer,DueTime,1000,@TimerAPCProc,nil,False) then  //使用了APC回调函数,回调函数的参数此例没有
  begin
    SleepEx(INFINITE,True);
  end;
  Result:=0;
end;

procedure TForm1.btn1Click(Sender: TObject);
var
  ID:DWORD;
begin
  CloseHandle(hTimer);
  hTimer:=CreateWaitableTimer(nil,True,nil); {建立定时器}
  CreateThread(nil,0,@MyFun,nil,0,ID);    {创建线程}
end;

procedure TForm1.btn2Click(Sender: TObject);
begin
  CancelWaitableTimer(hTimer);{取消定时器}
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  CloseHandle(hTimer);  {关闭句柄}
end;

end.

例4:在窗口标题上显示时间并自增计时

本例利用APC回调参数的第二个,第三个参数值获得时间并转换输出

//参数高低位时间>>合并成TFileTime(世界标准计时)>>LocalFileTime本地时间>>SystemTime系统时间>>Datetime
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    btn1: TButton;
    btn2: TButton;
    procedure btn1Click(Sender: TObject);
    procedure btn2Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  hTimer:THandle;

procedure TimerAPCProc(APointer:Pointer;dwTimerLowValue:DWORD;dwTimerHighValue:DWORD);stdcall;
var
  UTCFileTime,LocalFileTime:TFileTime;
  SystemTime:TSystemTime;
  DateTime:TDateTime;
begin
   {把 dwTimerLowValue 与 dwTimerHighValue 和并为一个 TFileTime 格式的时间}
  UTCFileTime.dwLowDateTime := dwTimerLowValue;
  UTCFileTime.dwHighDateTime := dwTimerHighValue;

  FileTimeToLocalFileTime(UTCFileTime, LocalFileTime); {从世界标准计时到本地时间}
  FileTimeToSystemTime(LocalFileTime, SystemTime);     {转到系统格式时间}
  DateTime := SystemTimeToDateTime(SystemTime);        {再转到 TDateTime}

  Form1.Text:=DateTimeToStr(DateTime);
  SleepEx(INFINITE,True);     {在回调参数里加这一句,会不断的循环}
end;

function MyFun(p:Pointer):integer;stdcall;
var
  DueTime:Int64;
begin
  DueTime:=0;
  {SetWaitableTimer 必须与 SleepEx 在同一线程}
  if SetWaitableTimer(hTimer,DueTime,1000,@TimerAPCProc,nil,False) then  //使用了APC回调函数
  begin
    SleepEx(INFINITE,True);
  end;
  Result:=0;
end;

procedure TForm1.btn1Click(Sender: TObject);
var
  ID:DWORD;
begin
  CloseHandle(hTimer);
  hTimer:=CreateWaitableTimer(nil,True,nil); {建立定时器}
  CreateThread(nil,0,@MyFun,nil,0,ID);    {创建线程}
end;

procedure TForm1.btn2Click(Sender: TObject);
begin
  CancelWaitableTimer(hTimer);{取消定时器}
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  CloseHandle(hTimer);  {关闭句柄}
end;

end.
例5:根据鼠标移动事件得到坐票在窗体上出现若干个时间计时

本例利用APC回调参数的第一个指针传递坐标

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    procedure FormDestroy(Sender: TObject);
    procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  hTimer:THandle; {等待计时器句柄}
  pt:TPoint;      {用来传递坐标}

procedure TimerAPCProc(APointer:Pointer;dwTimerLowValue:DWORD;dwTimerHighValue:DWORD);stdcall;
var
  UTCFileTime,LocalFileTime:TFileTime;
  SystemTime:TSystemTime;
  DateTime:TDateTime;
  pt2:TPoint;
begin
   {把 dwTimerLowValue 与 dwTimerHighValue 和并为一个 TFileTime 格式的时间}
  UTCFileTime.dwLowDateTime := dwTimerLowValue;
  UTCFileTime.dwHighDateTime := dwTimerHighValue;

  FileTimeToLocalFileTime(UTCFileTime, LocalFileTime); {从世界标准计时到本地时间}
  FileTimeToSystemTime(LocalFileTime, SystemTime);     {转到系统格式时间}
  DateTime := SystemTimeToDateTime(SystemTime);        {再转到 TDateTime}

  pt2:=PPoint(APointer)^; {接受第一个指针参数坐标 }
  Form1.Canvas.Lock;
  Form1.Canvas.TextOut(pt2.x,pt2.Y,DateTimeToStr(DateTime)); {取XY为坐标}
  Form1.Canvas.Unlock;

  SleepEx(INFINITE,True);  {此句可做循环}
end;

function MyFun(p:Pointer):integer;stdcall;
var
  DueTime:Int64;
begin
  DueTime:=0;
  {SetWaitableTimer 必须与 SleepEx 在同一线程}
  if SetWaitableTimer(hTimer,DueTime,1000,@TimerAPCProc,@pt,False) then  //使用了APC回调函数
  begin
    SleepEx(INFINITE,True);  {此句用做循环}
  end;
  Result:=0;
end;


procedure TForm1.FormDestroy(Sender: TObject);
begin
  CloseHandle(hTimer);  {关闭句柄}
end;



procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  ID:DWORD;
begin
  pt:=Point(x,y); {把XY坐票给pt}
  if hTimer = 0 then hTimer:=CreateWaitableTimer(nil,True,nil);
  CreateThread(nil,0,@MyFun,nil,0,ID);
end;

end.

总结:

1.主线程做类似循环输出占用资源会容易卡住,使用Application.ProcessMessages虽然可以解决卡顿,可是却会让循环停下。

2.当需要用多线程安排时,就要用到临界,互斥,信号量,事件,等待计时器(较复杂),以下根据需求作说明:

   临界:多个线程,一个一个进,用完一个再继续下一个。

   互斥:接力棒,谁拿到是谁的。(看等待函数放哪和释放语句放哪,可多个抢着进行,也可一个个运行。)

   信号量:可设置线程总数和先运行的数量。

   事件:可对事件相关的线程进行暂停,开始,步进后暂停。

   等待计时器:可根据需要设定为马上(0),相对时间,绝对时间运行;另外APC队伍调度级别高,时间精确度也比TTimer高。

https://www.cnblogs.com/chaosc/p/5817216.html