【Delphi】多线程编程

本篇简单介绍一下Delphi下多线程的编写

一、CreateThread

这个是最原始最基础的方法,利用Windows API来创建

function ThreadProc(param: LPVOID): DWORD; stdcall;
begin
  Result := 0;
end;

var
  threadId: TThreadID;
begin
  CreateThread(nil, 0, @ThreadProc, nil, 0, threadId);
end;

通过CreateThread来创建一个新线程,并执行给定的回调函数,回调函数签名参考微软官方手册:https://msdn.microsoft.com/zh-cn/f0dc203f-200e-42f1-940c-24e3fe080175

Delphi为了满足RTL自身功能需求,简单地包装了下CreateThread成为一个新的方法,它就是BeginThread

function ThreadProc(param: Pointer): Integer;
begin
  Result := 0;
end;

var
  threadId: TThreadID;
begin
  BeginThread(nil, 0, @ThreadProc, nil, 0, threadId);
end;

BeginThread内部依然是调用的CreateThread。最明显的一个区别是BeginThread的回调函数没有stcall标记

最大的区别是对异常的处理,原始CreateThread中如果发生异常,整个程序就默默地退出,而BeginThread创建的线程异常的话,会被默认的异常处理程序捕获,它退出时起码会弹个窗告诉你一声

function ThreadProc1(param: LPVOID): DWORD; stdcall;
begin
  raise Exception.Create('Error Message');
  Result := 0;
end;

function ThreadProc2(param: Pointer): Integer;
begin
  raise Exception.Create('Error Message');
  Result := 0;
end;

var
  threadId: TThreadID;
begin
  //CreateThread(nil, 0, @ThreadProc1, nil, 0, threadId);
  BeginThread(nil, 0, @ThreadProc2, nil, 0, threadId);
end;

BeginThread详细说明见官方文档:http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.BeginThread

二、TThread类

上面介绍的方法自然是比较底层比较C Style。Delphi提供了TThread这个类,只要继承它实现一个方法即可,用class的方式更OOP

type
  TMyThread = class(TThread)
  protected
    procedure Execute; override;
  end;

procedure TMyThread.Execute;
begin
  OutputDebugString('TMyThread.Execute()');
end;

// 调用
var
  t: TThread;
begin
  t := TMyThread.Create(True);  // True表示挂起线程,暂不启动。默认为False
  t.FreeOnTerminate := True; // 表示线程执行完毕后自动Free
  t.Start;  // 启动线程
end;

如果我们只是想在线程中执行一小段代码,无论方式一或者方式二都不是很优雅,要写很多代码。

更简单的方式是使用匿名线程

TThread.CreateAnonymousThread(
  procedure
  begin
    OutputdebugString('匿名线程');
  end).Start;

TThread.CreateAnonymousThread是静态方法,给一个lambda函数即可。这个方法内部也仅仅是创建了一个叫TAnonymousThread的类返回而已,和我们上面的TMyThread做的事情是一样的。

三、线程池

前面两种方式都是这样的执行过程:创建线程 > 执行函数 > 销毁线程。

其中线程的创建和销毁是比较占用CPU资源的,为了解决效率问题,所以有了线程池的概念。线程池中有若干线程处于挂起状态,即拿即用,不会有频繁的创建、销毁过程,提高了执行效率,适合频繁调用线程的场景

procedure TForm1.Button1Click(Sender: TObject);
begin
  TThreadPool.Default.QueueWorkItem(
    procedure
    begin
      OutputdebugString(PChar(Format('当前线程:%d', [GetCurrentThreadId])));
    end);
end;

TThreadPool.Default返回一个默认的线程池,QueueWorkItem添加一个任务请求,注意我的是请求,如果线程池的工作线程到达上限的话是需要等待的,这是池和普通CreateThread不同的一个地方。

点击按钮两次,看到IDE日志窗口

线程池中默认没有线程,只有在点击第一次时会新建一个线程,当我点击第二次时,由于上次的线程已经处于空闲状态,所以可以直接用,于是日志上没有"Thread Start"的消息了。

四、Task

Task本质上也是使用的线程池,但是提供了比ThreadPool更丰富的功能,Task是可控的

procedure TForm1.Button1Click(Sender: TObject);
var
  task: ITask;
begin
  task := TTask.Create(
    procedure
    begin
      OutputdebugString('11111');
      Sleep(2000);
    end);
  task.Start;
  task.Wait;  // 等待任务结束
  OutputdebugString('22222');
end;

使用静态方法Run立即启动一个Task

procedure TForm1.Button1Click(Sender: TObject);
var
  task: ITask;
begin
  task := TTask.Run(
    procedure
    begin
      OutputdebugString('11111');
      Sleep(2000);
    end);
  task.Wait;  // 等待任务结束
  OutputdebugString('22222');
end;

创建任务组也是可以的

procedure TForm1.Button1Click(Sender: TObject);
var
  tasks: array of ITask;
begin
  SetLength(tasks, 3);
  tasks[0] := TTask.Run(
    procedure
    begin
      OutputdebugString('task 1');
      Sleep(2000);
    end);
  tasks[1] := TTask.Run(
    procedure
    begin
      OutputdebugString('task 2');
      Sleep(2000);
    end);
  tasks[2] := TTask.Run(
    procedure
    begin
      OutputdebugString('task 3');
      Sleep(2000);
    end);

  TTask.WaitForAll(tasks);
  OutputdebugString('22222');
end;

使用Future来获取任务返回值

procedure TForm1.Button1Click(Sender: TObject);
var
  task: ITask;
  f: IFuture<Integer>;
  n: Integer;
begin
  f := TTask.Future<Integer>(
    function: Integer
    begin
      Sleep(1000);
      Result := 5;
    end);

  n := f.Value;  // 阻塞,等到任务执行完毕
end;

 

发布了98 篇原创文章 · 获赞 193 · 访问量 36万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 像素格子 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览