delphi 协程 开发 大型复杂 高性能 程序,TGormTabsBar、开发类似浏览器的程序,可无限扩展功能

前言

RAD Studio Athens 12.0 添加了一个新的 FormTabsBar 控件,作为通用且即用型的解决方案,用于在现代选项卡状用户界面中托管多个子表单,是在 VCL 中创建全新应用程序的一种方式。

这句话是官方的介绍,您应该知道,随着程序的功能日渐增多,传统的界面无论怎么设计都已经无法满足,继续新增的功能,以至于 现在流行一种 新的程序界面的设计方式,左侧导航、右侧 tab 选项卡,类似浏览器,这种界面将是以后 通用 功能繁多 软件的主流设计;

如下图:

image

那么说下问题:

问题一:与浏览器看着一样,实际一样吗??

浏览器 是多进程的,一个选项卡 一个进程,而delphi 不是多进程,还是在一个进程里,我们先看下浏览器:

image

浏览器面对的是 各种第三方 别人开发的网站,web技术日新月异的迭代,各种漏洞层出不穷,浏览器面对的外部环境不可控,很糟糕,设计成这种多进程的模式,主要是为了确保稳定性,你打开了一个选项卡进入了一个网站,若这个网站利用了漏洞,或死循环,您可以立即关闭掉这个选项卡,然后浏览器整体还能正常运行,多进程的优势就是 在复杂,糟糕的外部环境下,依然能稳定运行,劣势就是实现起来复杂,性能低下,浏览器这种技术 在 移动端是很难 火起来的,因为性能慢,臃肿 浪费电,若是PC端,电能是源源不断的,而移动端 电不给力,移动端 在未来 很长一段时间 都会流行 APP,而不是 web 技术;

问题二:我们自己开发的软件也要多进程吗???

完全没有必要,浏览器 之所以多进程,是被逼无奈的,外部环境 太复杂,太糟糕,完全不可控,就好比你不知道,哪个美女广告,你点开后,进入了一个利用浏览器漏洞,放马的网站,你不知道用户会打开什么网址;而我们自己开发的软件,每一个form都是我们自己设计开发的,完全可控,每一个form就相当于 一个网址,外部环境很好,没有必要 去搞多进程,多进程会 大幅度牺牲性能、带来复杂度、降低可维护性,仅仅是被逼无奈为了那么一点稳定性;微信小程序,也是没有采用多进程的模式,为什么呢,因为小程序需要上架 会被审核,也会被定期扫描,会被直接下架,小程序是被微信官方完全控制的,就好比 你能在浏览器上打开的网址是被完全控制的,还担心什么呢,不需要担心别人放马;浏览器的多进程模式 完全是因为外部不可控的 无奈之举;

问题三:若一个选项卡里的代码正在运行,关闭了这个选项卡怎么办?

比如 你打开一个选项卡 form1,里面有一个按钮 ,这个按钮的 onclick事件里 写上 发送 100个http请求,然后把响应结果展示在form1的ui界面上,结果发送到第33个时候,用户关闭了这个选项卡,form1.free,form1被释放了,那么第34个http请求响应的内容 去在ui上展示的时候,就会面临 Access Violation 错误,form1内存已经被释放!!就这么一个简单的 例子,你就会发现 代码执行的过程中,用户关闭选项卡带来的糟糕;

解决方法 就是 要在这个 form1里 使用协程,比如 这100个http请求,你有多种写法,举例:

go.bg(一个协程A).scope(form1).start,这个协程的作用域设置为 form1,然后 协程A里衍生 100个子协程,每个子协程不需要设置 作用域了,每个子协程就是简单的发送 http请求了,这个 form1关闭时 协程A,及其下面挂载的 100个http请求都会被连带关闭;很简单吧,协程的威力就体现出来了;当然你也可以不生成协程A,而是直接生成100个协程,每个协程都给其设置作用域到 form1上,也是没有问题的,只是没有必要每个协程都去设置作用域;

实例

以上面的问题2,做个实例吧,先用官方的 FormTabsBar + 普通的TForm 来实现,然后再用多线程的方式来实现,最后再用 协程的方式来实现;

主线程 + FormTabsBar + 普通的TForm

这种方式也是 普通开发者的开发方式;

image

main1 和 test.form1 两个窗体,代码如下:

image

procedure TForm1.Button1Click(Sender: TObject);
begin
  var httpClient := THTTPClient.Create;
  try
    for var i := 0 to 1000 do
    begin
      var httpResponse := httpClient.Get('http://vv.video.qq.com/checktime?otype=json');
      if httpResponse.StatusCode = 200 then
      begin
        var rspBody := httpResponse.ContentAsString(TEncoding.UTF8);
        Memo1.Lines.Add(rspBody);
      end else begin
        raise Exception.Create('请求失败,状态码:' + httpResponse.StatusCode.ToString);
      end;

      Application.ProcessMessages; //防止界面直接卡死,一般程序员都是通过这个
    end;
  finally
    httpClient.Free;
  end;
end;

当在运行的过程中,我们关闭选项卡,看下效果:

image

你会发现出现了 access violation 异常,原因是 窗体form1 关闭了,free掉了,主线程 还在发送 http请求 把结果展示在 memo1上,memo1不存在了;

多线程 + FormTabsBar + 普通的TForm

我们把它修改成多线程模式看下,form2的代码如下:

image

unit test.form2;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Net.HttpClient;

type
  TForm2 = class(TForm)
    Label2: TLabel;
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  /// <summary>
  /// 定义一个线程
  /// </summary>
  THttpThread = class(TThread)
  private
    FMemo: TMemo;
    procedure UpdateMemo(AText: string);
  protected
    procedure Execute; override;
  public
    constructor Create(AMemo: TMemo);
  end;

implementation

{$R *.dfm}

constructor THttpThread.Create(AMemo: TMemo);
begin
  inherited Create(True);
  FreeOnTerminate := True;
  FMemo := AMemo;
end;

procedure THttpThread.UpdateMemo(AText: string);
begin
  FMemo.Lines.Add(AText);
end;

procedure THttpThread.Execute;
begin
  var httpClient := THTTPClient.Create;
  try
    var httpResponse := httpClient.Get('http://vv.video.qq.com/checktime?otype=json');
    if httpResponse.StatusCode = 200 then
    begin
      var rspBody := httpResponse.ContentAsString(TEncoding.UTF8);

      Application.ProcessMessages;//防止UI界面卡死

      //使用 Synchronize 方法在主线程中更新 TMemo 组件的内容
      Synchronize(procedure
      begin
        UpdateMemo(rspBody);
      end);
    end else begin
      raise Exception.Create('请求失败,状态码:' + httpResponse.StatusCode.ToString);
    end;
  finally
    httpClient.Free;
  end;
end;

procedure TForm2.Button1Click(Sender: TObject);
begin
  for var I := 1 to 50 do //这里要搞小些不要上来搞1000,线程的代价是很高的,界面很卡
  begin
    THttpThread.Create(Memo1).Start;
    Application.ProcessMessages;
  end;
end;

procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;

end.

效果图,可以看到,关闭窗体后,依然是 access violation,原因和只用主线程是一样的,窗体关闭了,form2.free了,线程还要去访问 Memo1,肯定会报 access异常:

image

协程 + GormTabsBar + TGorm

用协程很轻松,就能解决上面的问题,以下是举例:

image

image

image

form3的完整代码:

unit test.form3;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Net.HttpClient, doroutine;

type
  TForm3 = class(TGorm)
    Label1: TLabel;
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure oneHttpGet(go: TGo); // bg协程,用于并行发送请求
    procedure showResponse(go: TGo); //ui协程,用于展示
  end;


implementation

{$R *.dfm}

procedure TForm3.Button1Click(Sender: TObject);
begin
  for var i := 0 to 1000 do //直接1000个协程,你几万也是没有问题的
  begin
    go.bg(oneHttpGet).start; //1000个协程 并行
  end;
end;

procedure TForm3.showResponse(go: TGo);
begin
  Memo1.Lines.Add(go.getString('rspBody'));
end;

procedure TForm3.oneHttpGet(go: TGo);
begin
  var httpClient := THTTPClient.Create;
  try
    var httpResponse := httpClient.Get('http://vv.video.qq.com/checktime?otype=json');
    if httpResponse.StatusCode = 200 then
    begin
      //分支UI协程,让其展示出来
      go.ui(showResponse).data('rspBody', httpResponse.ContentAsString(TEncoding.UTF8)).start;

    end else begin
      raise Exception.Create('请求失败,HTTP返回代码(' + httpResponse.StatusCode.ToString + '):' + httpResponse.ContentAsString(TEncoding.UTF8));
    end;
  finally
    httpClient.Free;
  end;
end;

end.

点击 buttton1后,我们创建了1000个协程并行,你一次性创建几万个协程,也是没有问题的,多线程是做不到的,每个bg协程就是发送 Http请求 ,然后 分支出ui协程, 把结果 展示在 memo上;效率非常高,界面不卡顿,完全不需要 Application.ProcessMessages; 一切平稳并行,关闭选项卡 什么的 根本不会报 access violation 异常,选项卡关闭,就相当于作用域关闭,与此作用域有关的一切协程,无论是 未运行的、正在被运行的、已经运行的 等等 统统都会立即 KO!

看下效果:

image

posted @ 2025-03-02 20:01  殴阳疯  阅读(141)  评论(0)    收藏  举报