delphi 协程 开发 大型复杂 高性能 程序,TGormTabsBar、开发类似浏览器的程序,可无限扩展功能
前言
RAD Studio Athens 12.0 添加了一个新的 FormTabsBar 控件,作为通用且即用型的解决方案,用于在现代选项卡状用户界面中托管多个子表单,是在 VCL 中创建全新应用程序的一种方式。
这句话是官方的介绍,您应该知道,随着程序的功能日渐增多,传统的界面无论怎么设计都已经无法满足,继续新增的功能,以至于 现在流行一种 新的程序界面的设计方式,左侧导航、右侧 tab 选项卡,类似浏览器,这种界面将是以后 通用 功能繁多 软件的主流设计;
如下图:

那么说下问题:
问题一:与浏览器看着一样,实际一样吗??
浏览器 是多进程的,一个选项卡 一个进程,而delphi 不是多进程,还是在一个进程里,我们先看下浏览器:

浏览器面对的是 各种第三方 别人开发的网站,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
这种方式也是 普通开发者的开发方式;

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

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;
当在运行的过程中,我们关闭选项卡,看下效果:

你会发现出现了 access violation 异常,原因是 窗体form1 关闭了,free掉了,主线程 还在发送 http请求 把结果展示在 memo1上,memo1不存在了;
多线程 + FormTabsBar + 普通的TForm
我们把它修改成多线程模式看下,form2的代码如下:

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异常:

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



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!
看下效果:


浙公网安备 33010602011771号