delphi异步与javascript

delphi及C++ Builder异步处理与javascript

目录

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运行时刻库说起:

1、用于实现异步事件、异步方法、及其异步结果回调的可自定义的通用类型


  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异步请求接口供外部传入。你可以实例化它们并传入该参数。

2、你可引用以下基于接口化对象和异步结果的接口的抽象类,去实现异步方法或异步事件的自定义类

2.1、用于实现所有异步过程调用的基类TBaseAsyncResult

        切勿将此实例作为实例引用传递。其目的是仅通过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;



2.2、IAsyncResult异步请求结果

        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、应用

3.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下载对话框中应用:很有用
  // =========匿名线程增强结束>>>>>>>>>>>>>>================

3.2、我们也习惯使用跨平台网络请求的“同步方法”来控制代码的“顺序”执行流程

    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、如何在处理本地长耗时的网络请求中直接使用“异步模型”

3.3.1、网络请求中的“异步模型”

        如上所述(3.2),如果我们关注每一种请求类型的3种异步“重载”方法,比如Get请求:

    ///

向url发送异步“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;

delphi异步与javascript_第1张图片

        在后台线程中执行该方法及其回调,并加入线程池统一管理该线程: 

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、异步模型与同步模型的比较

  • 同步模型:
  •         灵活性:你可以用自定义线程和线程池,管理你的应用中来自网络的和非网络的任务,“不阻塞”的执行、或让其“阻塞”的执行。
  •         功能性:取决于你的应用和代码的“设计模式”的架构与规划。
  •         可扩展性:较好,完全取决于“功能性”。
  •         风险性:如果不精通线程的设计与调度,可能会发生不可预期的“用户态”的系统错误。特别是 I / O冲突,包括来自网络的i/o及来自磁盘读写的i/o(磁盘i/o速度明显低于网络、内存及cpu的处理速度),所以如果在服务端访问第三方平台的API,在并发环境中你可能会期望”使用线程“来避免阻塞,此时,就特别要处理好CPU和I/O的协同和步调,要力争使其步调协调;另外,关于”并行“一般不建议未经特别的代码处理直接用于服务端,它会使cpu多核心同时工作高负荷运行,一般只在特别时段、处理特别事务中使用;否则在处理来自客户端请求的高并发环境中,会使得cpu感觉”疲惫“而响应客户的UE体验。
  • 异步模型:
  •         灵活性:不可自主,一般是做底层封装,如果您有能力也可自行封装;通常是由你使用的编程工具或IDE对接各操作系统底层的“通信模型”来具体实现的。
  •         功能性:受制于编程工具或IDE对各操作系统的“线程模型”的类型封装。
  •         可扩展性:“系统性”较强,取决于编程工具或IDE厂商的规划与进度。比如,浏览器的内核及其开发工具,对H5规范中js的Promise及ES6中async异步模型的语言特征的统一支持和跨平台功能实现。
  •         风险性:较小,编程工具或IDE厂商会系统性地QA及QC处理。I/O方面,它内部已经以统一的模式进行了处理(当然不够灵活)。

4、javascript中的多线程

4.1、javascript是单线程的吗

        “javascript是单线程的!”,网上流行这种说法,说的人多了,貌似也就“约定俗成”了。

        其实这种说法是不够严谨的,确切的说,“单线程”和“多线程”,本身从概念上讲,是程序“运行时”的属性,某段程序,如果它不被执行或调用,就谈不上说“单线程”还是“多线程”;因而“单线程”还是“多线程”,是程序代码在“运行时”的上下文环境。在程序执行的“上下文环境”中,是单个线程在工作,还是多个线程在同时工作或协同,最初,因为电脑的CPU都是单核心的,我们知道单个CPU在单位时间内就只能作一个worker处理一件“事务”,当时,就只有“单线程”这一种模型;后来,多核心的CPU出现了,多个CPU能够能在同一时刻分别处理不同的事情。

        “javascript是单线程”的说法,跟javascript的成长历史有关,最初,它就是为网页做些个“特效”而出现的,当时的浏览器也只是做一些个技术类“文章”写作与共享,浏览器也期望能丰富“互联网”的上的“应用”的范围和实效。那个年代,在服务端,根本没有javascript的身影,只在“浏览器”中应用一些简单功能。

4.2、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

你可能感兴趣的:(多线程,底层编程,网络请求,网络请求的异步模型,网络请求的同步模型,异步模型和同步模型,typescript)