zoukankan      html  css  js  c++  java
  • delphi 线程教学第七节:在多个线程时空中,把各自的代码塞到一个指定的线程时空运行

    第七节:在多个线程时空中,把各自的代码塞到一个指定的线程时空运行
       
    以 Ado 为例,常见的方法是拖一个 AdoConnection 在窗口上(或 DataModule 中),
    再配合 AdoQuery ,DataSoure, DbGrid 等,就可以实现数据库的访问操作。
    这种方式,可以理解为在主线程时空中访问数据库。
    如果某一个查询或更新操作耗时较长,那么,界面将会假死。
     
    1.在线程时空中访问数据库
     
    其实,上文中提到的这个 AdoConnection 不单只能在主线程时空中访问,
    亦可以在另一个多线程时空里访问。但有一个重要的前提:
    即:任意时刻,只能是一个线程访问 AdoConnection !
    可以是多线程时空访问,也可以是主线程时空访问,它们可以交替访问,但不能同时访问。
     
    故此,我们可以定义一个 DataThread 线程时空,把其它线程时空中需要访问数据库的操作,
    全部塞入到 DataThread 时空中。具体做法是在 DataThread 中设计一个接口,如下:
     
    unit uJooThread;
    interface
    uses
      Classes, uFooThread;
    type
     
      TJooThread = class(TFooThread)
      public
        procedure Synchronize(AProc: TThreadMethod);
      end;
     
    implementation
     
    { TJooThread }
     
    procedure TJooThread.Synchronize(AProc: TThreadMethod);
    begin
      ExecProcInThread(AProc);
      // 再设计一个等待 AProc 执行结果功能。
    end;
     
    end.
     
    这样既简化了其它线程访问数据库的操作,也避免了因为数据库操作耗时引起界面假死。
    另外,如果访问数据库发生了异常,重新 Create AdoConnection 然后再连接的操作也在这一个线程中进行,
    就不必考虑数据库操作的先后问题。如:不会出现需要访问数据库时, AoConnection 是损坏状态的情况。
    也就是说,用这个线程,把访问数据库的操作进行了排队。
     
    定义一个工作线程A, 先从数据库中取参数,再进行计算,然后把结果再更新到数据库。
    如果有10个线程A实例,那么线程A访问数据库的时候,只需要把访问操作塞进 DataThread 中即可。
     
    有朋友肯定会有疑问,为何不为每一个线程指定一个 AdoConnection 。这样编程将会变得简单。
    一、数据库的连接是会消耗资源的,连接数是有上限的。
    二、如果每个线程的 AdoConnection 产生的操作都是访问同一个表。
    那么数据库自身会上锁,将它们变成串行执行,并不能提高效率。
     
    2.多个客户端,同时从数据库取参数会如何?
     
    假设每个客户端通过一个 AdoStoredProcduce (存储过程)来获取参数,我们可以在此存储过程中,
    加入数据库的 sp_getapplock (以 MSSQL为例)来让客户端按顺序获取到参数。更新亦同理。
     
    由此可见,学习了多线程的锁,亦能推而广之理解数据库的锁。
     
    以下是详细代码,至此,本教程的任务基本完成。
    本例的调用方法就不写了。如果你读懂了所有的教程,应该会写了。
    如果没读懂,写了又有什么意义呢?请恶补面向对象的基础知识。
    以后的其它教程,均以本多线程教程为基础。
     
    unit uJooThread;
    interface
    uses
      Classes, SyncObjs, uFooThread, uFooList;
    type
      PSyncRec = ^TSyncRec;
      TSyncRec = record
        Method: TThreadMethod; // 这是类的方法
        Proc: TThreadProcedure; // 这是匿名方法
        // 本例只写了类的方法。需要匿名方法,请自行重载 Sync 与 Queue
        Signle: TEvent;
        Queue: boolean;
      end;
     
      TSyncRecList = class(TFooList<PSyncRec>) //用于装执行代码的 List
      protected
        procedure FreeItem(Item: PSyncRec); override;
      end;
     
      TJooThread = class(TFooThread)
      private
        FSyncRecList: TSyncRecList;
        procedure Check;
      public
        constructor Create(ACanAccessCom: boolean);
        destructor Destroy; override;
        procedure Synchronize(AProc: TThreadMethod); // 阻塞到 AProc执行完毕才返回。
        procedure Queue(AProc: TThreadMethod); // 塞入线程后立即返回。
      end;
      // 本例就是前面单节讲的知识的综合运用。
      // TEvent,FooThread,FooList,全都用上了。
      // 并构建了一个新的线程功能。
      // 当我写完以后发现,与系统源码中,
      // 窗口接收 WM_NULL 消息后的处理UI操作的功能,几乎是一模一样的。
      // 不同的是,本例是在线程时空,系统源码是在主线程时空。
    implementation
    { TJooThread }
    procedure TJooThread.Check;
    var
      p: PSyncRec;
    begin
      FSyncRecList.Lock; // 所有要执行的代码,都在这个 List 中了。
      // 此处是线程时空,故从List 中取出并执行代码即可。
      try
        p := nil;
        if FSyncRecList.Count > 0 then // 每次取 List 的第一个来执行。
        begin
          p := FSyncRecList[0];
          FSyncRecList.Delete(0);
        end;
      finally
        FSyncRecList.Unlock;
      end;
     
      if Assigned(p) then
      begin
     
        if Assigned(p.Method) then
          p.Method
        else if Assigned(p.Proc) then
          p.Proc();
     
        if not p.Queue then // 如果是阻塞,就置信号。
          p.Signle.SetEvent;
     
        Dispose(p);
     
        ExecProcInThread(Check);
      end;
    end;
     
    constructor TJooThread.Create(ACanAccessCom: boolean);
    begin
      inherited;
      FSyncRecList := TSyncRecList.Create;
    end;
     
    destructor TJooThread.Destroy;
    begin
      FSyncRecList.Free;
      inherited;
    end;
     
    procedure TJooThread.Queue(AProc: TThreadMethod);
    var
      p: PSyncRec;
    begin
      FSyncRecList.Lock;
      try
        new(p);
        FSyncRecList.Add(p);
        p.Method := AProc;
        p.Queue := true;
        ExecProcInThread(Check);
      finally
        FSyncRecList.Unlock;
      end;
    end;
     
    procedure TJooThread.Synchronize(AProc: TThreadMethod);
    var
      p: PSyncRec;
      o: TEvent;
    begin
      FSyncRecList.Lock;
      try
        new(p);
        FSyncRecList.Add(p);
        p.Method := AProc;
        o := TEvent.Create(niltruefalse'');
        p.Signle := o;
        p.Queue := false;
        ExecProcInThread(Check); //触发线程启动
      finally
        FSyncRecList.Unlock;
      end;
      o.WaitFor; // 等待 AProc 执行完毕的信号
      o.Free;
    end;
     
    { TSyncRecList }
    procedure TSyncRecList.FreeItem(Item: PSyncRec);
    begin
      inherited;
      if Assigned(Item.Signle) then
        Item.Signle.Free;
      Dispose(Item);
    end;
     
    end.
     
     
  • 相关阅读:
    MATLAB批量读入图片
    MATLAB小技巧
    Ubuntu下OpenCV不能被某个python版本识别
    切换Ubuntu系统python默认版本的方法
    LoadRunner内部结构(2)
    LoadRunner例子:检查点为参数的一个例子
    LoadRunner中字符串的操作
    LoadRunner脚本实例来验证参数化的取值
    LoadRunner编程之文件的操作
    LoadRunner关联函数的脚本实例--如何操作关联参数
  • 原文地址:https://www.cnblogs.com/lackey/p/6337357.html
Copyright © 2011-2022 走看看