目录
delphi及C++ Builder异步处理与javascript
1、用于实现异步事件、异步方法、及其异步结果回调的可自定义的通用类型
2、你可引用以下基于接口化对象和异步结果的接口的抽象类,去实现异步方法或异步事件的自定义类
2.1、用于实现所有异步过程调用的基类TBaseAsyncResult
2.2、IAsyncResult异步请求结果
3、应用
3.1、我们不经意地习惯使用“同步模型”来控制代码的“顺序”执行流程
3.2、我们也习惯使用跨平台网络请求的“同步方法”来控制代码的“顺序”执行流程
3.3、如何在处理本地长耗时的网络请求中直接使用“异步模型”
4、javascript中的多线程
4.1、javascript是单线程的吗
4.2、javascript线程的执行上下文
本客户相关博文,喜欢的就点个赞,鼓励我的原创技术写作:
继上一篇《Delphi中的匿名方法》中谈到的匿名方法,本文进一步叙述用该匿名方法,实现的异步处理。仍然是从System.Classes底层系统的RTL运行时刻库说起:
TAsyncProcedureEvent = procedure (const ASyncResult: IAsyncResult) of object;
TAsyncFunctionEvent = procedure (const ASyncResult: IAsyncResult; out Result: TObject) of object;
TAsyncConstArrayProcedureEvent = procedure (const ASyncResult: IAsyncResult; const Params: array of const) of object;
TAsyncConstArrayFunctionEvent = procedure (const ASyncResult: IAsyncResult; out Result: TObject; const Params: array of const) of object;
TAsyncConstArrayProc = reference to procedure (const Params: array of const);
TAsyncConstArrayFunc = reference to function (const Params: array of const): TResult;
TAsyncCallback = reference to procedure (const ASyncResult: IAsyncResult);
TAsyncCallbackEvent = TAsyncProcedureEvent;
这些可自定义具体实现方法的通用类型,均提供了IAsyncResult异步请求接口供外部传入。你可以实例化它们并传入该参数。
切勿将此实例作为实例引用传递。其目的是仅通过IAsyncResult接口引用此对象。不注意这一警告将导致无法预测的行为。请参阅Invoke方法的相关信息。
// System.Classes
TBaseAsyncResult = class abstract(TInterfacedObject, IAsyncResult)
private type
TAsyncFlag = (Completed, Synchronous, Invoked, Cancelled, ForceSize = SizeOf(Integer) * 8 - 1); // make the size the same as Integer.
TAsyncFlags = set of TAsyncFlag;
private
class constructor Create;
class destructor Destroy;
private
FContext: TObject;
FAsyncFlags: TAsyncFlags;
FInvokingThread: TThreadID;
FAsyncHandle: TMultiWaitEvent;
procedure SetFlagsAtomic(Value, Mask: TAsyncFlags);
function GetAsyncContext: TObject;
function GetAsyncWaitEvent: TMultiWaitEvent;
function GetCompletedSynchronously: Boolean;
function GetIsCompleted: Boolean;
function GetIsCancelled: Boolean;
property AsyncWaitEvent: TMultiWaitEvent read GetAsyncWaitEvent;
protected
///
/// This field will hold the acquired exception instance raised from the execution of the async method call.
/// It will be re-raised in the context of the invoking thread when the corresponding EndXXXX method is called.
///
FInvokingException: TObject;
///
/// Override this method to dispatch the actual asynchronous procedure call. Descendants will use whatever context
/// or other state information contained in the instance to pass along to the procedure or function.
///
procedure AsyncDispatch; virtual; abstract;
///
/// Override this method to perform any extra state or signaling required by the descendant. The descendant must
/// call this inherited method in order to properly set the completion and possibly signal the FAsyncHandle if
/// previously created. Failure to call this method can result in a dead lock or hang.
///
procedure Complete; virtual;
///
/// Calls the actual target asynchronous method within the context of however it is scheduled. This could be
/// in the context of the main or GUI thread, or within a background thread. This depends on the implementation
/// of a specific asynchronous BeginXXXX method call.
///
procedure DoAsyncDispatch;
///
/// Override this method to schedule the asynchronous procedure call in the manner specific to
/// a given instance, component or class. By default, this will schedule the async procedure onto
/// the main thread or execute the procedure synchronously if already on the main thread.
/// Other classes may schedule the procedure call into a separate thread or thread pool.
///
procedure Schedule; virtual;
///
/// This constructor must be called from a descendent protected constructor.
///
constructor Create(const AContext: TObject); overload;
///
/// Opaque user-supplied context. This context is available via the IAsyncResult.GetAsyncContext and descendents
/// if this class.
///
property Context: TObject read FContext;
///
/// Returns true if the operation can be cancelled. When cancelling the async operation, do any additional processing.
///
///
/// By default, all Async cannot be cancelled. If descendants support cancelling asynchronous tasks,
/// they must override this behaviour and do the required processing;
///
function DoCancel: Boolean; virtual;
public
///
/// This constructor should never be called directly. Only descendents should be constructed using the
/// protected Create constructor above. Calling this constructor will raise an exception.
///
constructor Create; overload;
destructor Destroy; override;
///
/// Cancels the async operation. Returns True when the asynchronous operation can be cancelled.
///
function Cancel: Boolean;
///
/// This method must be called prior in order to return itself as an IAsyncResult and actually schedule/invoke the
/// async call.
///
function Invoke: IAsyncResult;
///
/// As long as the rules for only ever accessing this object through the IAsynsResult interface, this method
/// should only ever be called by a given "EndInvoke" method by casting the IAsyncResult interface instance
/// back to a specific descendant instance of this class. Never call this method directly outside the context
/// of an "EndInvoke" style method.
///
procedure WaitForCompletion;
///
/// This method is called from VCL.TControl (and possibly other similar) descendants in order to call the
/// asynchronous procedure/function as a result of a posted Window message.
///
class procedure Dispatch(const AsyncResult: TBaseAsyncResult); reintroduce; static; inline;
///
/// Set to True when the asynchronous call has been cancelled.
///
property IsCancelled: Boolean read GetIsCancelled;
end;
IAsyncResult异步请求结果:是系统级别的RTL运行时刻库,分别实现了跨平台的API接口,供你使用,它来自System.Types:
///
/// 从THTTPClient或继承于它的TRestClient客户端网络通讯库的各种“BeginXXX”方法返回的接口,以提供代码的异步执行:
///
IAsyncResult = interface
///
/// 返回与此实例关联的用户指定上下文:
///
function GetAsyncContext: TObject;
///
/// 返回一个适合用于阻塞的事件,直到异步调用完成。此事件也适合在列表中使用,以允许等待所有或任何信号。请参阅TMultiWaitEvent。WaitForXXX类函数:
///
function GetAsyncWaitEvent: TMultiWaitEvent;
///
/// 如果给定的异步调用能够同步完成,则返回true。换句话说,特定调用在返回之前完成:
///
function GetCompletedSynchronously: Boolean;
///
/// 异步调用完成时返回True:
///
function GetIsCompleted: Boolean;
///
/// 取消异步调用后返回True:
///
function GetIsCancelled: Boolean;
///
/// 取消异步操作。当可以取消异步调用时,返回True:
///
function Cancel: Boolean;///
/// 只读属性:与此实例关联的用户指定上下文:
///
property AsyncContext: TObject read GetAsyncContext;
///
/// 只读属性:事件。适用于阻塞,直到异步调用完成。此事件也适合在列表中使用,以允许等待所有或任何信号。请参阅TMultiWaitEvent。WaitForXXX类函数:
///
property AsyncWaitEvent: TMultiWaitEvent read GetAsyncWaitEvent;
///
/// 只读属性:如果给定的异步调用能够同步完成,则设置为true。换句话说,特定调用在返回之前完成:
///
property CompletedSynchronously: Boolean read GetCompletedSynchronously;
///
/// 只读属性:异步调用完成时设置为True:
///
property IsCompleted: Boolean read GetIsCompleted;
///
/// 只读属性:取消异步调用后设置为True:
///
property IsCancelled: Boolean read GetIsCancelled;
end;
你可以将IAsyncResult异步请求结果作为参数,传入章节1所叙述的各种异步事件或其回调类型( 1、用于实现异步事件、异步方法、及异步结果回调的通用类)。
3.1.1、具名线程的同步
type
ThreadWechat = class(TThread)
private
FifTerminated: Boolean; //:外部控制参数_控制线程执行过程中强行中断并释放线程
//F_GetTickCount_:Cardinal;//:_为了兼容IDE版本_重写继承函数GetTickCount
FTerminated: Boolean; //:继承字段_属性Terminated读
/// 线程当前的处理步骤:
FStep:string;
/// 线程执行Execute中_当前微信实例在执行的_不采用匿名方法的函数:
FProcObject:TThreadparamStrMethod;//procedure(var aStr:string;aCurWechat:TObject) of object;
procedure _Sync_;
/// 线程执行Execute中_当前微信实例aCurWechat在执行什么API事务aStr:
procedure doExecute_Api(var aStr:string;aCurWechat:TObject);
/// 线程同步Synchronize中_当前微信实例aCurWechat在同步什么API事务aStr:
procedure doSynchronize_Api(var aStr:string;aCurWechat:TObject);
//:以下,用上面通用Execute及其Synchronize替代_需要外部传入FApi的字符串及FcurWechat及FcurProgressBar来识别:
protected
/// 微信类实例__是否终止了线程的执行:
property Terminated: Boolean read FTerminated; //property ReturnValue:Integer read FReturnValue write FReturnValue;
/// 微信类实例__线程执行函数:
procedure Execute; override;
/// 线程自旋计时_为了兼容不同的IDE版本:
function _GetTickCount_: Cardinal;
/// 供外部询问线程处理的当前步骤:
property FStep_No:string read FStep;
/// 供外部询问线程处理的当前方法:
property FProc_WhatMethod:TThreadparamStrMethod read FProcObject;
public
/// 传入_当前线程处理的当前微信实例调用哪个Api唯一标识_通用的不采用匿名方法:
FApi:string;
/// 传入_当前线程处理的当前微信实例唯一标识_通用的不采用匿名方法:
FcurWechat:TObject;
/// 传入_当前线程处理的当前微信实例的唯一同步的进度条_通用的不采用匿名方法:
FcurProgressBar:TObject;//TObject; TProgressBar
/// 传入_线程执行Execute中_当前微信实例要执行的类方法函数_不采用匿名方法的函数:
FProcObjectSync:TTickCountMethod;//procedure of Object;
constructor Create(CreateSuspended: Boolean);
destructor Destroy;
end;
3.1.1、匿名线程的同步
procedure TFormGY.PopupMenu2Click(Sender: TObject);
begin
SJGY.ShowALayoutMenu_DisposeOf(self);
// ====这是名称为01的等待对话框=====
SJGY.MyWaitShow('正在查询,请稍后......', self, '01', TAlphaColorRec.White, TAlphaColorRec.Red, TAlphaColorRec.Red); // 显示等待提示动画
TThread.CreateAnonymousThread( // 创建一个单线程,完成ATask
procedure
begin // 线程里的代码写这里(确保不要有对界面元素的操作)
// 如保存到数据库
sleep(5000); // 这里模拟等待5秒钟
TThread.Synchronize(nil,
procedure // 界面交互代码要用这个包起来,在主线程中执行
begin
SJGY.MyWaithide(self, '01'); // 隐藏等待提示动画
SJGY.ToastConfirm('查询完毕!', self, 1);
end);
end).Start;
end;
3.1.2、匿名线程升级版的同步模型
// 欢迎使用【通用跨平台移动框架】,详询欢迎加QQ群:174483085
TAnonymousThread = class(TThread)
private
class var FRunningThreads: TList;
private
FThreadFunc: TFunc;
FOnErrorProc: TProc;
FOnFinishedProc: TProc;
FaT: T1;
FResult: T2;
FStartSuspended: boolean;
private
procedure ThreadTerminate(Sender: TObject);
protected
procedure Execute; override;
public
constructor Create(aT: T1; AThreadFunc: TFunc;
AOnFinishedProc: TProc; AOnErrorProc: TProc;
ACreateSuspended: boolean = False; AFreeOnTerminate: boolean = True);
class constructor Create;
class destructor Destroy;
end; //:来超备注2021-10-12:TAnonymousThread:SJGY.ShowDownLoadDialog下载对话框中应用:很有用
// =========匿名线程增强结束>>>>>>>>>>>>>>================
try
MyHTTPResponse := MyHTTPClient.Get(LowerCase(Wx_ApiProxy + Getkflist + '?') + queryString, nil, nil );
Result_SyncCode:= MyHTTPResponse.StatusCode; // = 200 本系统的返回码__非微信的
try
ResultJsonStr_MyHTTPResponse := (MyHTTPResponse.ContentAsString(TEncoding.UTF8));
// 客服头像的URL链接,服务端不应当URL解码给客户端否则不安全,客户端解析的时候注意应当URL解码“\/”等特殊符号__TNetEncoding.URL.Decode
finally
if Assigned(JO1_MyHTTPResponse) then JO1_MyHTTPResponse.DisposeOf;
end;
except // Wx异常有异常时专用的响应 :
try
ResultJsonStr_MyHTTPResponse := (MyHTTPResponse.ContentAsString(TEncoding.UTF8));
JO1_MyHTTPResponse := TJSONObject.ParseJSONValue( ResultJsonStr_MyHTTPResponse ) as TJSONObject;
if JO1_MyHTTPResponse <> nil then
begin
Result_SyncCode:= JO1_MyHTTPResponse.GetValue('errcode');
Resolution_WxAPIErrcode( Result_SyncCode); // 错误处理
end;
finally
ResultJsonStr_MyHTTPResponse := '';
if Assigned(JO1_MyHTTPResponse) then JO1_MyHTTPResponse.DisposeOf;
end;
3.2.1、你会习惯性地使用:
function THTTPClient.Get(const AURL: string; const AResponseContent: TStream; const AHeaders: TNetHeaders): IHTTPResponse;
function THTTPClient.Post(const AURL: string; const ASourceFile: string;
const AResponseContent: TStream; const AHeaders: TNetHeaders): IHTTPResponse;
function THTTPClient.Put(const AURL, ASourceFile: string;
const AResponseContent: TStream; const AHeaders: TNetHeaders): IHTTPResponse;
function THTTPClient.Patch(const AURL: string; const ASource, AResponseContent: TStream;
const AHeaders: TNetHeaders): IHTTPResponse;
function THTTPClient.Delete(const AURL: string; const AResponseContent: TStream; const AHeaders: TNetHeaders): IHTTPResponse;
function THTTPClient.Options(const AURL: string; const AResponseContent: TStream;
const AHeaders: TNetHeaders): IHTTPResponse;
function THTTPClient.GetRange(const AURL: string; AStart, AnEnd: Int64; const AResponseContent: TStream;
const AHeaders: TNetHeaders): IHTTPResponse;
function THTTPClient.Trace(const AURL: string; const AResponseContent: TStream; const AHeaders: TNetHeaders): IHTTPResponse;
function THTTPClient.Head(const AURL: string; const AHeaders: TNetHeaders): IHTTPResponse;
等等,它们,从本质上来讲,都是“阻塞式”的“网络请求”,即在网络客户端默认的或你自定义的请求和响应超时时间内,阻塞式地完成请求并返回响应的接口,直到返回响应或返回超时为止。内部,它是这样工作的:
// System.Net.HttpClient.pas
function THTTPClient.Execute(const ARequest: IHTTPRequest; const AContentStream: TStream; const AHeaders: TNetHeaders): IHTTPResponse;
var
LHeader: TNetHeader;
begin
if ARequest.IsCancelled then
(ARequest as THTTPRequest).DoResetCancel;if AHeaders <> nil then //: 遍历请求头,设置Header头的键值对数值 :
for LHeader in AHeaders do
ARequest.SetHeaderValue(LHeader.Name, LHeader.Value);// System.Net.URLClient.pas
// : 获取响应的实例并执行它,但它内部直接忽略了三个异步参数为nil, nil, nil, :
Result := DoGetResponseInstance(Self, nil, nil, nil, ARequest, AContentStream) as IHTTPResponse;
ExecuteHTTP(ARequest, AContentStream, Result);
end;// DoGetResponseInstance的原型:
function TURLClient.DoGetResponseInstance( const AContext: TObject; // 执行上下文的实例 const AProc: TProc; // 传入的异步执行的匿名方法 const AsyncCallback: TAsyncCallback; // 需要回调的异步方法 const AsyncCallbackEvent: TAsyncCallbackEvent; // 需要回调的异步事件 const ARequest: IURLRequest; // URI请求的接口类型的实例 const AContentStream: TStream // 响应的内容流 ): IAsyncResult; // 返回异步结果的操作系统API实现的接口类型的实例 begin raise ENetURIClientException.CreateRes(@SNetSchemeFunctionNotImplemented); // 当网络发生故障时,返回错误提示字符串并释放相关的内部资源引用 end;
可见,同步模型下,直接忽略了三个异步模型相关的类型参数:
const AProc: TProc; // 传入的异步执行的匿名方法
const AsyncCallback: TAsyncCallback; // 需要回调的异步方法
const AsyncCallbackEvent: TAsyncCallbackEvent; // 需要回调的异步事件
3.2.2、即:默认的各种请求,我们是没有刻意地去引用“响应接口”的“异步结果接口属性”的:
IURLResponse = interface(IInterface)
['{5D687C75-5C36-4302-B0AB-989DDB7558FE}']
//......(省略)
// 默认的各种请求,我们是没有刻意地去引用异步结果接口属性的:
/// AsyncResult异步结果接口属性的Getter:
function GetAsyncResult: IAsyncResult;
/// 对异步结果接口IAsyncResult的引用,用于控制相应请求的异步执行:
property AsyncResult: IAsyncResult read GetAsyncResult;
end;
一旦,你引用了“异步结果接口属性”,或者直接用3.3、中的“异步模型”进行网络请求,那么,请求及其响应,将会以“异步的方式”被执行。
3.2.2、请求引用“异步结果接口属性”执行异步响应
var aAsyncResult : IAsyncResult;
var aAsyncWaitResult : TWaitResult;
var aMultiWaitEvent : TMultiWaitEvent;
var aWaitStr : string;
if access_token='' then
begin
Result := GetJsonString('access_token不能为空');
exit;
end;
MyHTTPClient := THTTPClient.Create;
MyHTTPClient.ConnectionTimeout := 5000; // 5秒
MyHTTPClient.ResponseTimeout := 10000; // 10秒
MyHTTPClient.ContentType := 'application/json; charset=UTF-8';
MyHTTPClient.UserAgent := 'Embarcadero URI Client/1.0';
MyHTTPClient.AutomaticDecompression := [
THTTPCompressionMethod.Deflate,
THTTPCompressionMethod.GZip,
THTTPCompressionMethod.Brotli,
THTTPCompressionMethod.Any ];
queryString:='access_token='+access_token;
try
aAsyncResult := MyHTTPClient.Get(
LowerCase(Wx_ApiProxy + Getkflist + '?')
+ queryString, nil, nil
).AsyncResult;
aMultiWaitEvent := aAsyncResult.AsyncWaitEvent.create;
aAsyncWaitResult := aMultiWaitEvent.waitfor(0);
while ( aAsyncWaitResult >= 1 ) do // 内部线程的原子信号量等待__不阻塞
begin
break;
end; //TWaitResult = (wrSignaled, wrTimeout, wrAbandoned, wrError, wrIOCompletion);
//TWaitResult = (0, 1, 2, 3, 4);
aMultiWaitEvent.SetEvent; // 显式的发出信号
if aAsyncResult.IsCompleted then // :如果网络请求被异步地执行完毕,无论对与错,那么:
begin
if aAsyncWaitResult = TWaitResult.wrTimeout then aWaitStr :='网络超时...';
if aAsyncWaitResult = TWaitResult.wrError then aWaitStr :='网络错误...';
if aAsyncWaitResult = TWaitResult.wrAbandoned then aWaitStr :='请求被放弃了...';
if aAsyncWaitResult = TWaitResult.wrIOCompletion then aWaitStr :='I/O完成了...';
if aAsyncWaitResult = TWaitResult.wrSignaledResult then
begin
aWaitStr :='正确返回...';
// 拿到响应结果后,你想做的事情......
end;
end;
finally
MyHTTPClient.DisposeOf;
end;
3.2.3、但是,的确,你很少会去关注和直接使用它们分别拥有的3种异步“重载”方法:
3.3.1、网络请求中的“异步模型”
如上所述(3.2),如果我们关注每一种请求类型的3种异步“重载”方法,比如Get请求:
///
function BeginGet(const AURL: string; const AResponseContent: TStream = nil;
const AHeaders: TNetHeaders = nil): IAsyncResult; overload;
function BeginGet(const AsyncCallback: TAsyncCallback; const AURL: string; const AResponseContent: TStream = nil;
const AHeaders: TNetHeaders = nil): IAsyncResult; overload;
function BeginGet(const AsyncCallbackEvent: TAsyncCallbackEvent; const AURL: string;
const AResponseContent: TStream = nil; const AHeaders: TNetHeaders = nil): IAsyncResult; overload;
我们便能够拿到一个“异步结果”的接口IAsyncResult。并且后两种异步“重载”方法,还可以执行方法TAsyncCallback或事件TAsyncCallbackEvent的“异步回调”(其类型声明详见本文章节1:“1、用于实现异步事件、异步方法、及其异步结果回调的可自定义的通用类型”)。
// Result_AsyncCode:= -9999;
try
MyHTTPClient := THTTPClient.Create;
MyHTTPClient.ConnectionTimeout := 5000; // 5秒
MyHTTPClient.ResponseTimeout := 10000; // 10秒
MyHTTPClient.ContentType := 'application/json; charset=UTF-8';
MyHTTPClient.UserAgent := 'Embarcadero URI Client/1.0';
MyHTTPClient.AutomaticDecompression := [THTTPCompressionMethod.Deflate,THTTPCompressionMethod.GZip,THTTPCompressionMethod.Brotli,THTTPCompressionMethod.Any];
try
Result := ResultJsonStr_MyHTTPResponse;
// :如果是函数的话,在异步模型中,function的返回值已失去意义void(0),相当于一个procedure
ContentStreamResult:=TStringStream.Create('',TEncoding.UTF8);
AsyncCallback := TAsyncCallback(
procedure(AsyncResult:IAsyncResult)
begin
{ ---EndAsyncHTTP( 你的THttpClient异步请求 )拿到响应对象接口 : 异步若需等待并为调用者返回结果、或宝异常: 只能这样处理
// 如果你仅仅需要内部拦截等待:
aMultiWaitEvent := AsyncResult.AsyncWaitEvent.create;
aAsyncWaitResult := aMultiWaitEvent.waitfor(0);
while ( aAsyncWaitResult >= TWaitResult.wrSignaled ) do // 内部线程的原子信号量等待__不阻塞
begin
if aAsyncWaitResult <> TWaitResult.wrIOCompletion then break;
ResultJsonStr_MyHTTPResponse := ContentStreamResult.DataString;
end; //TWaitResult = (wrSignaled, wrTimeout, wrAbandoned, wrError, wrIOCompletion);
//TWaitResult = (0, 1, 2, 3, 4);
FreeAndNil(aMultiWaitEvent); // :异步这样虽然能拦截,但尚未能正确捕获异常 }
// 异步模型下,传入的内容流A,异步执行结果的响应对象的流B, 当响应结束后_无论成功或失败,A流的字节大小===B流的字节大小 :
if ( (AsyncResult as THttpResponse).GetContentStream.Size = ContentStreamResult.Size ) then //if ( (AsyncResult as THttpResponse).GetContentStream.Equals(ContentStreamResult) ) then
// if ( (AsyncResult as THttpResponse).GetStatusCode >=0 ) then // 相应类的实现THTTPResponse = class(TURLResponse, IHTTPResponse)
begin //TThread.Current.Synchronize(nil,procedure begin ShowMessage((AsyncResult as THttpResponse).GetStatusCode.ToString) end); // GetStatusCode = 200
// + sLineBreak + '异步结果IAsyncResult的执行上下文的类名...'+TObject(AsyncResult.AsyncContext).ClassName; // 异步结果IAsyncResult的执行上下文的类名...为该跨平台的THTTPClient库_比如MSWindows下为---TWinHTTPClient
ResultJsonStr_MyHTTPResponse:=ContentStreamResult.DataString;
TThread.Current.Synchronize(nil,procedure begin EnableLogMe:=true; LogMe(Application.MainForm,'ContentStreamResult.DataString:'+ResultJsonStr_MyHTTPResponse + sLineBreak + '异步结果IAsyncResult的执行上下文的类名...'+TObject(AsyncResult.AsyncContext).ClassName ); EnableLogMe:=false; end); // 内部日志
if Assigned(proc) then TThread.Current.Synchronize(nil,procedure begin proc(ResultJsonStr_MyHTTPResponse); end) else TThread.Current.Synchronize(nil,procedure begin ShowMessage('你不传入方法,我也没法为你做事并回传给你看'); end);
// :如果外部传入了方法,我就在【网络-异步模型】的内部按照这个方法帮你干!
if MyHTTPClient<>nil then MyHTTPClient.DisposeOf;
FreeAndNil(ContentStreamResult);
FreeAndNil(aStrlist);
end;
end); // 发出异步请求 :
MyHTTPResponse:=MyHTTPClient.EndAsyncHTTP( // : 异步若需等待并为调用者返回结果、或宝异常: 只能这样处理
MyHTTPClient.BeginGet( AsyncCallback, LRequestURL, ContentStreamResult, aHeaders )
);); // :这是异步调用;同步的话: MyHTTPClient.Get( LRequestURL, nil, nil );
except
HttpResult := '网络异常或服务器故障,请检查...'; // 捕获网络异常、服务器未启动或不工作等故障
end;
finally
if HttpResult = '网络异常或服务器故障,请检查...' then Result := HttpResult
else Result := MyHTTPResponse.ContentAsString(TEncoding.UTF8);
end;
原理:它内部的本质,仍旧是,后台线程的安全锁,底层代码如下:
// System.Net.HttpClient
function THTTPClient.InternalExecuteAsync(const AsyncCallback: TAsyncCallback; const AsyncCallbackEvent: TAsyncCallbackEvent;
const ARequest: IHTTPRequest; const AContentStream: TStream;
const AHeaders: TNetHeaders; AOwnsSourceStream: Boolean): IAsyncResult;
var
LHeader: TNetHeader;
LContentStream: TStream;
LAsync: IAsyncResult;
LRequest: THTTPRequest;
{$IFDEF AUTOREFCOUNT}
LSourceStream: TStream;
{$ENDIF}
begin
if ARequest.IsCancelled then
(ARequest as THTTPRequest).DoResetCancel;
if AHeaders <> nil then
for LHeader in AHeaders do
ARequest.SetHeaderValue(LHeader.Name, LHeader.Value);
LRequest := ARequest as THTTPRequest;
if AOwnsSourceStream then
LRequest.FOwnedStream := LRequest.FSourceStream
{$IFDEF AUTOREFCOUNT}
else
LSourceStream := LRequest.FSourceStream
{$ENDIF};
LContentStream := AContentStream;
LAsync := DoGetResponseInstance(Self,
procedure
begin
try
ExecuteHTTP(ARequest, LContentStream, (LAsync as THttpResponse));
// : !!!!关键!!!!!!!!LAsync as THttpResponse
// : 内部处理了线程中的网络请求的事件循环,响应流、Cookies管理、状态码、中断、与异常
// : 特别的,状态码,处理了200、及401和407的失败回调(如果前置的身份验证authentication失败,则回退到正常路径)
finally
{$IFDEF AUTOREFCOUNT}
LSourceStream := nil;
{$ENDIF}
end;
end, AsyncCallback, AsyncCallbackEvent, ARequest, AContentStream);
Result := LAsync;
// Invoke Async Execution.!!!!关键---线程中的请求结果及调度:!!!!!!!!
(LAsync as THttpResponse).Invoke;
end;
其中(详见:2.1、用于实现所有异步过程调用的基类TBaseAsyncResult):
// System.Classes
function TBaseAsyncResult.Invoke: IAsyncResult;
begin
SetFlagsAtomic([TAsyncFlag.Invoked], [TAsyncFlag.Invoked]);
FInvokingThread := TThread.CurrentThread.ThreadID;
// :当前执行请求的线程引用计数,返回异步请求结果,并进行事件、方法等调度 :
_AddRef;
Result := Self;
Schedule;
end;
3.3.2、处理本地长耗时的网络任务的两种“通用方法”
3.3.2.1、直接使用“异步请求”的网络模型、传入回调并执行
procedure TForm1.ThreadExec_WechatMethod(aProcName:string='');
var
proc: TProc;
//LWxApi:TWxApi; aProcObjectSync:TTickCountMethod;
//......(,仅为示例,详细,略)......
if ( trim(aProcName) = 'Customservice_Getkflist' ) then
begin//:___定义并启动___◆对话服务_获取所有的客服人员的列表___:
//callApiOfTWxApi_InThread( LWxApi, trim(aProcName) );
// :===============这是线程中的网络同步模型; 现在换一种方法__调用网络异步模型 : ========================================================
proc := procedure(aStr:string) begin {写入方法:}Memo1.Lines.Add(aStr); {传入事件:}Button_OKClick(Text_Debug); end;
HTTPClientHandler_Get_Async ( trim(aProcName), nil, TProc(proc) ); //: 传入空的回调方法则不会被执行 :TProc(nil)
FreeAndNil(LWxApi);
end;
end;
3.3.2.2、使用线程,在后台线程的同步函数中处理传入的“回调”
type
//TTickCountProcedure = TProcedure;
TTickCountMethod = procedure of Object;
// :某个类的实例方法
TThreadparamStrMethod = procedure(var aStr:string;aCurBaidupan:TObject) of Object;
// :线程类中带字符串及对象参数的类实例方法
定义需要在后台线程中执行的“网络请求”的通用方法:
procedure TForm1.ThreadExec_WechatMethod(aProcName:string='');
var
// proc: TProc; // 改用后台线程同步回调 :
LWxApi:TWxApi; aProcObjectSync:TTickCountMethod;
/// 类的实例中是否发布了该方法:
/// 下来请进一步研究System.Classes.TClassFinder
function ifMethodIsDefined(aClassInstance:TObject;aMethodName:string):Boolean;
var aMethodObject:TTickCountMethod;
begin
if (aClassInstance=nil) or (trim(aMethodName)='') then
begin
ShowMessage('在执行判断类实例是否定义了某方法的内部函数时,传参错误...'); Result:=false;
end else
begin
@aMethodObject:=aClassInstance.MethodAddress(aMethodName);
if @aMethodObject = nil then
begin
FreeAndNil(aClassInstance); ShowMessage('该API尚未定义无法调用...'); Result:=false;
end else Result:=true;
end;
end;
/// 返回已在类实例中发布的某方法:
/// 下来请进一步研究System.Classes.TClassFinder
function getMethodDefined(aClassInstance:TObject;aMethodName:string):TTickCountMethod;
var poinerRecordOfMethod:TMethod;// 方法("包含的指针的code及data")的记录类型
begin
Result := nil;
if (aClassInstance<>nil) and (trim(aMethodName)<>'') then
begin
poinerRecordOfMethod.Code := Pointer(aClassInstance.MethodAddress(aMethodName));
poinerRecordOfMethod.Data := Pointer(aClassInstance);
end;
if Assigned( poinerRecordOfMethod.Code ) then // 如果定义了该方法,则返回该方法
Result := TTickCountMethod( poinerRecordOfMethod );
// TProcedure<可选参数>针对function ; TTickCountMethod针对procedure (可选参数) of object
end;
/// 在线程池的某线程实例中执行对象池中微信类TWxApi的某实例中发布的某个API方法:
/// 微信类TWxApi的某个实例.
/// 微信类实例中发布的某个API方法.
///
procedure callApiOfTWxApi_InThread(aClassInstance:TObject;aMethodName:string);
begin
if ifMethodIsDefined( aClassInstance,trim(aMethodName) ) then
begin
aProcObjectSync := getMethodDefined( aClassInstance,trim(aProcName) );
FapiOfMyWechat.Add( aClassInstance );
ThreadExec_WechatMethod( aClassInstance, trim(aProcName), aProcObjectSync );
end;
end;
begin//___FapiOfMyWechat微信对象池___FaThreads_Wechats处理业务的后台线程池___
if FaThreads_Wechats = nil then FaThreads_Wechats := TList.Create;
//___产生微信类的实例___:
LWxApi := TWxApi.Create;
LWxApi.FStatus := stWxT_AddedtoList;//:微信对象实例的___运行状态类型仓库管理___:Ord(LWxApi.FStatus)=1
if ifMethodIsDefined( LWxApi,trim(aProcName) ) = false then exit;
if ( trim(aProcName) = 'Customservice_Getkflist' ) then
begin//:___定义并启动___◆对话服务_获取所有的客服人员的列表___:
callApiOfTWxApi_InThread( LWxApi, trim(aProcName) );
// :===============这是线程中的网络同步模型;
end;
end;
在后台线程中执行该方法及其回调,并加入线程池统一管理该线程:
procedure TForm1.ThreadExec_WechatMethod(aWxApi:TObject;aProcName:string;aProcObjectSync:TTickCountMethod);
var LThreadWechat:ThreadWechat;
begin
//定义线程:
LThreadWechat := ThreadWechat.Create(true);
LThreadWechat.FreeOnTerminate:=true;
LThreadWechat.FcurWechat := aWxApi; // :传入的微信类实例
LThreadWechat.FApi := aProcName; // :传入调用的●API的方法函数名●
LThreadWechat.FProcObjectSync := aProcObjectSync;//LWxApi.Get_access_token;
//线程加入列表___线程池___统一管理便于将来释放等处理:
FaThreads_Wechats.Add( LThreadWechat );
//唤醒并启动运行(列表___线程池中的)该线程:
try
LThreadWechat.Resume; //ThreadWechat( FaThreads_Wechats.Items[ Lcount_WxApi ] )
CheckSynchronize;
finally
end;
end;
3.3.3、异步模型与同步模型的比较
“javascript是单线程的!”,网上流行这种说法,说的人多了,貌似也就“约定俗成”了。
其实这种说法是不够严谨的,确切的说,“单线程”和“多线程”,本身从概念上讲,是程序“运行时”的属性,某段程序,如果它不被执行或调用,就谈不上说“单线程”还是“多线程”;因而“单线程”还是“多线程”,是程序代码在“运行时”的上下文环境。在程序执行的“上下文环境”中,是单个线程在工作,还是多个线程在同时工作或协同,最初,因为电脑的CPU都是单核心的,我们知道单个CPU在单位时间内就只能作一个worker处理一件“事务”,当时,就只有“单线程”这一种模型;后来,多核心的CPU出现了,多个CPU能够能在同一时刻分别处理不同的事情。
“javascript是单线程”的说法,跟javascript的成长历史有关,最初,它就是为网页做些个“特效”而出现的,当时的浏览器也只是做一些个技术类“文章”写作与共享,浏览器也期望能丰富“互联网”的上的“应用”的范围和实效。那个年代,在服务端,根本没有javascript的身影,只在“浏览器”中应用一些简单功能。
4.2.1、服务端
在多核心CPU硬件环境下,nodejs能够处理和运行”多线程“。
4.2.2、客户端
在浏览器或嵌入式浏览器(比如App中的webview)环境下,在网页的主线程(即浏览器进程内置js的解释引擎中,负责处理该页面的”渲染线程“ [ 渲染:不熟悉浏览器及其js工作原理的朋友,可以理解为UI界面在被像素化地”绘制“出来之前的内容的生成和呈现过程 ])中,浏览器只允许javascript代码,以”单个线程“的方式进行作业,这,是由js的宿主,浏览器来决定的。
js代码如何在多线程环境下工作?
多核环境下:(1)、js代码,可以同时运行在”不同页面对应的后台导航器中Navigator,即我们通常所说的专用worker“(即浏览器的非”渲染“子进程);(2)、不同的js模块,它们还可以工作于浏览器的”后台服务worker“中。(3)、1个个单个的js模块,它也可以分别同时运行在浏览器的不同”标签页“中,通过”多标签页“共享资源。头两种,均不可直接操作负责渲染的页面的”DOM“;而且,不同浏览器的兼容性也各异;当然,绝大部分的功能,绝大部分浏览器的最新版本都基本支持了。
的确,还是有不少限制的,毕竟js还是”年轻的“。
另外就是,js是”弱类型“的,代码多了,特别是”堆“大了,很容易引发因”类型“问题导致的”不宜被发现“的异常。typescript的诞生和逐渐成熟,将会彻底改变和补充这一现状,使得js能支持”强类型“。顺便说下ts,他的发明人和主导者,Anders Hejlsberg安德斯·海尔斯伯格,即咱们一直在使用的Turbo Pascal语言编译器、delphi及c++ 的Builder的IDE的发明人,他还是C#之父,.NET的创立者。
1、多线程
2、RTTI运行时的类型信息
3、在delphi和C++ Builder中使用JavaScript
4、JavaScript