delphi异步与javascript

news2025/1/15 17:16:36

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<TResult> = 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
    /// <summary>
    ///    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.
    /// </summary>
    FInvokingException: TObject;
    /// <summary>
    ///    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.
    /// </summary>
    procedure AsyncDispatch; virtual; abstract;
    /// <summary>
    ///    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.
    /// </summary>
    procedure Complete; virtual;
    /// <summary>
    ///    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.
    /// </summary>
    procedure DoAsyncDispatch;
    /// <summary>
    ///    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.
    /// </summary>
    procedure Schedule; virtual;
    /// <summary>
    ///    This constructor must be called from a descendent protected constructor.
    /// </summary>
    constructor Create(const AContext: TObject); overload;
    /// <summary>
    ///    Opaque user-supplied context. This context is available via the IAsyncResult.GetAsyncContext and descendents
    ///    if this class.
    /// </summary>
    property Context: TObject read FContext;
    ///  <summary>
    ///    Returns true if the operation can be cancelled. When cancelling the async operation, do any additional processing.
    ///  </summary>
    ///  <remarks>
    ///    By default, all Async cannot be cancelled. If descendants support cancelling asynchronous tasks,
    ///  they must override this behaviour and do the required processing;
    ///  </remarks>
    function DoCancel: Boolean; virtual;
  public
    /// <summary>
    ///    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.
    /// </summary>
    constructor Create; overload;
    destructor Destroy; override;

    ///  <summary>
    ///    Cancels the async operation. Returns True when the asynchronous operation can be cancelled.
    ///  </summary>
    function Cancel: Boolean;
    /// <summary>
    ///    This method must be called prior in order to return itself as an IAsyncResult and actually schedule/invoke the
    ///    async call.
    /// </summary>
    function Invoke: IAsyncResult;
    /// <summary>
    ///    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.
    /// </summary>
    procedure WaitForCompletion;

    /// <summary>
    ///    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.
    /// </summary>
    class procedure Dispatch(const AsyncResult: TBaseAsyncResult); reintroduce; static; inline;

    ///  <summary>
    ///    Set to True when the asynchronous call has been cancelled.
    ///  </summary>
    property IsCancelled: Boolean read GetIsCancelled;
  end;



2.2、IAsyncResult异步请求结果

        IAsyncResult异步请求结果:是系统级别的RTL运行时刻库,分别实现了跨平台的API接口,供你使用,它来自System.Types

  /// <summary>
  ///    THTTPClient或继承于它的TRestClient客户端网络通讯库的各种“BeginXXX”方法返回的接口,以提供代码的异步执行:
  /// </summary>
  IAsyncResult = interface
    ///  <summary>
    ///    返回与此实例关联的用户指定上下文:
    ///  </summary>
    function GetAsyncContext: TObject;
    ///  <summary>
    ///    返回一个适合用于阻塞的事件,直到异步调用完成。此事件也适合在列表中使用,以允许等待所有或任何信号。请参阅TMultiWaitEvent。WaitForXXX类函数:
    ///  </summary>
    function GetAsyncWaitEvent: TMultiWaitEvent;
    ///  <summary>
    ///    如果给定的异步调用能够同步完成,则返回true。换句话说,特定调用在返回之前完成:
    ///  </summary>
    function GetCompletedSynchronously: Boolean;
    ///  <summary>
    ///    异步调用完成时返回True:
    ///  </summary>
    function GetIsCompleted: Boolean;
    ///  <summary>
    ///    取消异步调用后返回True:
    ///  </summary>
    function GetIsCancelled: Boolean;
    ///  <summary>
    ///    取消异步操作。当可以取消异步调用时,返回True:
    ///  </summary>
    function Cancel: Boolean;

    ///  <summary>
    ///    只读属性:与此实例关联的用户指定上下文:
    ///  </summary>
    property AsyncContext: TObject read GetAsyncContext;
    ///  <summary>
    ///    只读属性:事件。适用于阻塞,直到异步调用完成。此事件也适合在列表中使用,以允许等待所有或任何信号。请参阅TMultiWaitEvent。WaitForXXX类函数:
    ///  </summary>
    property AsyncWaitEvent: TMultiWaitEvent read GetAsyncWaitEvent;
    ///  <summary>
    ///    只读属性:如果给定的异步调用能够同步完成,则设置为true。换句话说,特定调用在返回之前完成:
    ///  </summary>
    property CompletedSynchronously: Boolean read GetCompletedSynchronously;
    ///  <summary>
    ///    只读属性:异步调用完成时设置为True:
    ///  </summary>
    property IsCompleted: Boolean read GetIsCompleted;
    ///  <summary>
    ///    只读属性:取消异步调用后设置为True:
    ///  </summary>
    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读
    /// <summary>线程当前的处理步骤:</summary>
    FStep:string;
    /// <summary>线程执行Execute中_当前微信实例在执行的_不采用匿名方法的函数:</summary>
    FProcObject:TThreadparamStrMethod;//procedure(var aStr:string;aCurWechat:TObject) of object;

    procedure _Sync_;
    /// <summary>线程执行Execute中_当前微信实例aCurWechat在执行什么API事务aStr:</summary>
    procedure doExecute_Api(var aStr:string;aCurWechat:TObject);
    /// <summary>线程同步Synchronize中_当前微信实例aCurWechat在同步什么API事务aStr:</summary>
    procedure doSynchronize_Api(var aStr:string;aCurWechat:TObject);
      //:以下,用上面通用Execute及其Synchronize替代_需要外部传入FApi的字符串及FcurWechat及FcurProgressBar来识别:

  protected
    /// <summary>微信类实例__是否终止了线程的执行:</summary>
    property Terminated: Boolean read FTerminated; //property ReturnValue:Integer read FReturnValue write FReturnValue;
    /// <summary>微信类实例__线程执行函数:</summary>
    procedure Execute; override;
    /// <summary>线程自旋计时_为了兼容不同的IDE版本:</summary>
    function _GetTickCount_: Cardinal;
    /// <summary>供外部询问线程处理的当前步骤:</summary>
    property FStep_No:string read FStep;
    /// <summary>供外部询问线程处理的当前方法:</summary>
    property FProc_WhatMethod:TThreadparamStrMethod read FProcObject;
  public
    /// <summary>传入_当前线程处理的当前微信实例调用哪个Api唯一标识_通用的不采用匿名方法:</summary>
    FApi:string;
    /// <summary>传入_当前线程处理的当前微信实例唯一标识_通用的不采用匿名方法:</summary>
    FcurWechat:TObject;
    /// <summary>传入_当前线程处理的当前微信实例的唯一同步的进度条_通用的不采用匿名方法:</summary>
    FcurProgressBar:TObject;//TObject; TProgressBar
    /// <summary>传入_线程执行Execute中_当前微信实例要执行的类方法函数_不采用匿名方法的函数:</summary>
    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<T1, T2> = class(TThread)
  private
    class var FRunningThreads: TList<TThread>;
  private
    FThreadFunc: TFunc<T1, T2>;
    FOnErrorProc: TProc<Exception>;
    FOnFinishedProc: TProc<T1, T2>;
    FaT: T1;
    FResult: T2;
    FStartSuspended: boolean;
  private
    procedure ThreadTerminate(Sender: TObject);
  protected
    procedure Execute; override;
  public
    constructor Create(aT: T1; AThreadFunc: TFunc<T1, T2>;
      AOnFinishedProc: TProc<T1, T2>; AOnErrorProc: TProc<Exception>;
      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<integer>('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}']
    //......(省略)
    // 默认的各种请求,我们是没有刻意地去引用异步结果接口属性的:
    /// <summary>AsyncResult异步结果接口属性的Getter:</summary>
    function GetAsyncResult: IAsyncResult;
    /// <summary>对异步结果接口IAsyncResult的引用,用于控制相应请求的异步执行:</summary>
    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请求:

    /// <summary>向url发送异步“GET”命令</summary>
    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
          { // 如果你需要内部拦截等待:
          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);  // 发出异步请求 :
        MyHTTPClient.BeginGet( AsyncCallback, LRequestURL, ContentStreamResult, aHeaders );
        // :这是异步调用;同步的话: MyHTTPClient.Get( LRequestURL, nil, nil );
      except
        // ...(略)
      end;
    finally
    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<string>;
    //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<string>(proc) ); //: 传入空的回调方法则不会被执行 :TProc<string>(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<string>; // 改用后台线程同步回调 :
    LWxApi:TWxApi; aProcObjectSync:TTickCountMethod;
    /// <summary>类的实例中是否发布了该方法:</summary>
    /// <remarks>下来请进一步研究System.Classes.TClassFinder</remarks>
    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;
    /// <summary>返回已在类实例中发布的某方法:</summary>
    /// <remarks>下来请进一步研究System.Classes.TClassFinder</remarks>
    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;
    /// <summary>在线程池的某线程实例中执行对象池中微信类TWxApi的某实例中发布的某个API方法:</summary>
    /// <param name="aClassInstance">微信类TWxApi的某个实例.</param>
    /// <param name="aMethodName">微信类实例中发布的某个API方法.</param>
    /// <remarks></remarks>
    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、异步模型与同步模型的比较

  • 同步模型:
  •         灵活性:你可以用自定义线程和线程池,管理你的应用中来自网络的和非网络的任务,“不阻塞”的执行、或让其“阻塞”的执行。
  •         功能性:取决于你的应用和代码的“设计模式”的架构与规划。
  •         可扩展性:较好,完全取决于“功能性”。
  •         风险性:如果不精通线程的设计与调度,可能会发生不可预期的“用户态”的系统错误。特别是 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/74503.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于DDoS攻击,这些基本概念你一定要知道!

什么是DDoS攻击 DDoS是Distributed Denial of Service的简称&#xff0c;中文是分布式拒绝服务。 这有点拗口吧&#xff1f; 这样&#xff0c;我们先理解下DDoS的前身DoS&#xff08;Denial of Service&#xff09;&#xff0c;即拒绝服务。 最基本的DoS攻击就是攻击者利用…

基础--吊打面试官--精通synchronized底层实现原理

synchonized是一个字段 1.0之前太慢&#xff0c;重&#xff0c;jdk1.0后修改&#xff0c;变得轻.修改的原理是&#xff1a;以前是涉及到用户态和内核态的交互&#xff0c;现在是用户态实现。 基本概念理解&#xff1a; 用户态和内核态的概念&#xff1a;程序的不同级别。内核态…

Vue学习:事件处理(与用户产生交互-点击)

Vue对元素绑定事件&#xff0c;需要使用指令&#xff0c;也就是v-开头 v-on&#xff1a;当什么什么时候时候 点击-出现弹窗&#xff1a;使用method方法 <!-- 准备容器 --><div idroot> <h2>欢迎页面&#xff0c;你好 {{name}}</h2><!-- v-on:click…

(附源码)小程序 法律全书 毕业设计 280844

小程序spring boot法律全书管理系统 摘 要 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;小程序法律全书被用…

计算机毕业设计ssm+vue基本微信小程序的灾情救助系统 uniapp 小程序

项目介绍 自新冠疫情爆发以来,给各行各业带来了前所未有的挑战。国务院、党中央高度重视灾情救助工作,研究出一系列重要的措施和方法。并广泛利用信息化技术手段,对灾情信息发布、救助管理等进行管理。对于受灾地区来说,存在管理难度大,手段单一,灾民流动性复杂等问题。单靠人…

git 暂存当前分支修改,切换到别的分支

收藏&#xff0c;原文链接Git学习记录-git保留/丢弃当前分支修改并切换至其他分支 - 爱写bug的程序员 - 博客园 (cnblogs.com) 笔者在本地终端进行 git 工作目录的相关处理时&#xff0c;遇到由于某种情况需要使用 git checkout 命令切换到其他分支的情景。此时&#xff0c;若…

3 - 线程池 Java内置的线程池 - ExecutorService

1、ExecutorService的介绍 ExecutorService 接口继承了Executor 接口&#xff0c;是Executor 的子接口。 Executors类 提供工厂方法用来创建不同类型的线程池。Executors是工具类&#xff0c;他提供对ThreadPoolExecutor的封装&#xff0c;会产生几种线程池供大家使用。 关于…

derby 转 mysql

背景 nacos使用standalone方式启动&#xff0c;数据存储到内置的derby数据库中&#xff0c;现在要更改为集群启动&#xff0c;原数据要转成mysql。 执行过程 1.nacos内置的derby数据打包后放到本地 tar -cvf data.tar nacos/data/derby-data 2.通过idea添加Apache Derby&a…

【区块链】Ankr被黑引发的思考

机会 三明治交易、夹子机器人、抢跑、抢新、抢购、秒杀&#xff0c;相信这些词你都听说过了&#xff0c;区块链上的各种套利操作&#xff0c;基本上都有一个大前提&#xff0c;就是监听链上最新的未打包交易&#xff0c;才能在第一时间抢占先机。 前段时间Ankr被黑&#xff0…

进阶 - Git的自定义

Git的自定义 忽略特殊文件 有些时候&#xff0c;你必须把某些文件放到Git工作目录中&#xff0c;但又不能提交它们&#xff0c;比如保存了数据库密码的配置文件啦&#xff0c;等等&#xff0c;每次git status都会显示Untracked files ...&#xff0c;有强迫症的童鞋心里肯定不…

大学生网页制作期末作业——html+css+javascript+jquery旅游官网6页 html大学生网站开发实践作业 web网页设计实例作业

&#x1f468;‍&#x1f393;静态网站的编写主要是用 HTML DⅣV CSSJS等来完成页面的排版设计&#x1f469;‍&#x1f393;&#xff0c;一般的网页作业需要融入以下知识点&#xff1a;div布局、浮动定位、高级css、表格、表单及验证、js轮播图、音频视频Fash的应用、uli、下拉…

java alibaba fastjson自定义序列化反序列化(教你解决问题思路)

大家版本不一样方式可能不一样&#xff0c;我不管你的fastjson版本是哪个&#xff0c;按照我这个思路去弄就行 写一个JSONObject类&#xff0c;导入fastjson的JSONObject&#xff0c;然后CTRL鼠标左键点进去看JSONObject源码&#xff0c;然后点击IDEA的左上角select opened fil…

掘金量化如何精准选股?

说起掘金量化如何精准选股的这个问题&#xff0c;相信大家也很期待&#xff0c;就比如说我们在量化投资中&#xff0c;一方面对股票的涨跌是需要灵活的去判断&#xff0c;才能知晓这其中有哪些是我们应该选择的股票&#xff0c;但是要自己去观察&#xff0c;也是很麻烦&#xf…

【Python项目】毕业设计必备,Python基于面向对象+tkinter打造学生信息管理系统 | 附源码

前言 halo&#xff0c;包子们上午好 很多学计算机的小伙伴应该都知道&#xff0c;毕业设计是一个头疼的东西 今天的话小编这边给大家准备好了一个Python基于面向对象tkinter打造学生信息管理系统 这不是毕业设计必备项目 说实话操作起来还是有那么一点点的难度的&#xff0c;但…

Spring Boot源码学习:自动配置与自定义注解详解

入门 RestContrller :此注解标记的类下的 所有 方法均会返回一个 domain 对象以代替视图Controller、ResponseBody 的缩写使用 Jackson2 以及 MappingJackson2HttpMessageConverter 类自动转换对象为 JSONSpringBootApplication&#xff1a;快捷注释&#xff0c;包含以下内容C…

“新十条”来了,精准防控是为了“行稳致远”

文|螳螂观察 作者|松雅湖、小江 形势正在快速变化&#xff0c;朝越来越好的方向。 12月7日&#xff0c;国务院联防联控机制发布防疫“新十条”措施&#xff0c;指向精准&#xff0c;要求明确&#xff0c;既要疫情防控精准化、科学化&#xff0c;也要防疫成果“行稳致远”&am…

sentence Bert解读及代码示例

0-前序 Bert已经是相当6了&#xff0c;但在STS&#xff08;语义文本相似性&#xff09;任务中&#xff0c;需要将两个句子都输入到网络中&#xff0c;也就是说要过模型&#xff0c;这样计算量就大了。如下是文本相似性&#xff0c;并不是语义。 from transformers import Ber…

RabbitMQ、RocketMQ、Kafka 三大组件详细教程,一文带你学完全部知识

RabbitMQ RabbitMQ各组件的功能 Broker &#xff1a;一个RabbitMQ实例就是一个BrokerVirtual Host &#xff1a;虚拟主机。相当于MySQL的DataBase&#xff0c;一个Broker上可以存在多个vhost&#xff0c;vhost之间相互隔离。每个vhost都拥有自己的队列、交换机、绑定和权限机…

项目执行管理的8个步骤

常言道&#xff1a;说起来容易&#xff0c;做起来难。在项目执行过程中&#xff0c;许多事情可能会出错。这就是为什么执行过程中的监控和跟踪很重要。 项目执行&#xff0c;如同项目管理一样&#xff0c;被分解成若干步骤&#xff0c;以确保你不会忽视任何关键的东西。八个项…

如何查看Chrome浏览器的页面缓存内容【详细教程】

如何查看浏览器页面缓存内容——代码&控制台知识调用前言引入控制台输入代码查看在控制台application查看知识调用 文章可能需要用到的知识&#x1f525;&#x1f525;&#x1f525;浏览器缓存有哪些&#xff08;通用缓存有哪些&#xff09; 前言引入 浏览器有多种缓存&a…