多線程查詢

下面就是一个基本的继承TThread生成的多线程类。

  QuerThrd.Pas
  unit QuerThrd;
  interface
  uses
  Classes,DBTables;

  type
  TQueryThread=class(TThread)
  private
  fQuery:tQuery;
  protected
  procedure Execute;override;
  public
  constructorCreate(Suspended:Boolean;Query:TQuery);
  end;

  implementation
  constructor
  TQueryThread.Create(Suspended:Boolean;Query:TQuery);
  begin
  inheritedCreate(Suspended);
  fQuery:=Query;
  FreeOnTerminate:=True;
  end;

  procedure TQueryThread.Execute;
  begin
  fQuery.Open;
  end;
  end.

  在上面这个简单的例子中,我们构造了一个TThread的子类TQueryThread,用于在后台执行数据库查询。在该类的Create函数中,传递了两个参数Suspended和Query,其中Suspended用于控制线程的运行,如果Suspend为真,TQueryThread类的线程在建立后将立即被悬挂,一直到运行了Resume方法,该线程才会继续执行,Query参数用于接受一个已经存在的Query控件(在窗体中真正的Query控件)而使它在多线程的情况下运行。Execute是最重要的过程,它是类TQueryThread的执行部分,所有需要在这个多线程类中运行的语句都必须写在这个过程里。
  实际上构造自己的多线程类时,并不需要输入所有的这些代码,选择DELPHI的File菜单下的new选项,再选“TThreadObject”项目,DELPHI就会为你构造基本的程序模块。然后我们可以根据需要再做相应的修改。
  进程的执行:
  假设我们已经建立了一个窗体FORM1,窗体中有我们将要使用的查询控件Query1。那么我们在该单元的USES部分加入上面写的QuerThrd单元。

  procedure TForm1.Button1Click(Sender:TObject);
  begin
  {建立一个运行的进程}
  TQueryThread.Create(False,Query1);
  end;

  如果这个过程被执行,那么窗体中的查询控件Query1就会自动在多线程的环境下运行查询。注意TQueryThread类中只有Create而没有Free,动态建立类以后又忘记删除是我们常犯的错误之一,不过在这里由于我们指定了FreeOnTerminate(运行完即删除)为真,所以当Execute里的语句执行完后,TQueryThread类占据的内存控件将被自动释放。
  然而还有一个问题值得我们注意,由于同一时刻可以有多个线程同时运行,那么我们还必须解决好同步的问题,如果几个多线程程序之间没有任何关联,那么它们之间也不会有任何冲突。但实际上,可能同时运行几个多线程的数据库应用程序,由于需要共享相同的数据库资源,我们还需要为Query1增加一个Tsession控件。




给你一段代码
interface

uses
  Classes,messages,ADODB,DB,SysUtils,Dialogs,ActiveX;

type
  Tmythread = class(TThread)
  private
    Fquery:TAdoquery;            //查询
    FConnection:TAdoconnection;  //连接
    FDataSource: TDataSource;   { 与查询组件相关的数据感知组件         }
    procedure ConnectDataSource;{ 连接数据查询组件和数据感知组件的方法 }
  protected
    procedure Execute; override;
  public
    constructor Create(Suspended:Boolean;Query:TAdoQuery;DS:TDataSource); { 线程构造器 }
    destructor Destroy;override;
  end;

implementation
procedure Tmythread.ConnectDataSource;{ 连接数据查询组件和数据感知组件的方法 }
begin
   FDataSource.dataset:=Fquery;
end;
constructor Tmythread.Create(Suspended:Boolean;Query:TAdoQuery;DS: TDataSource);
begin
  CoInitialize( nil );  // 这个在线程中是必需的
  try
    Fconnection:=TAdoconnection.create(nil);
    Fquery:=Query;
    FDataSource:=DS;
    Fquery.connection:=Fconnection;
    with Fconnection do
    begin
      LoginPrompt:=False;
      KeepConnection:=True;
      ConnectionString:='Provider=MSDASQL.1;Persist Security Info=False;Data Source=honesty';
    end;
    inherited Create(Suspended);
  except
    on E: Exception do
    begin
      showmessage('创建线程时出错!错误是:'+ E.Message );
    end;
  end;
end;
destructor Tmythread.Destroy;
begin
  Fconnection.free; //释放
  CoUnInitialize;
  inherited Destroy;
end;
procedure Tmythread.Execute;
begin
  try
    FreeOnTerminate := true;
    FQuery.Open;                  { 打开查询 }
    Synchronize(ConnectDataSource); { 线程同步 }
  except
    showmessage('线程异常!');       {线程异常 }
  end;
end;

end.


没必要多线程吧,异步模式就可以了

分页……

 

posted on 2006-01-18 09:31 青蛙學堂 阅读(790) 评论(3)  编辑 收藏 引用 所属分类: 軟件布袋

评论

# re: 多線程查詢 2006-02-03 16:36 y

优秀的数据库应用应当充分考虑数据库访问的速度问题。通常可以通过优化数据库、优化 查询语句、分页查询等途径收到明显的效果。即使是这样,也不可避免地会在查询时闪现一个带有 SQL符号的沙漏,即鼠标变成了查询等待。最可怜的是用户,他(她)在此时只能无奈地等待。遇到急性子的,干脆在此时尝试 Windows中的其它应用程序,结果致使你的数据库应用显示一大片白色的窗口。真是无奈!

  本文将以简单的例子告诉你如何实现线程查询。还等什么,赶快打开Delphi对照着下面的完整源代码试试吧。

  在查询时能够做别的事情或者取消查询,这只是基本的线程查询,在你阅读了Delphi有关线程帮助之后能立刻实现。这里介绍的是多个线程查询的同步进行。

  在Delphi数据库应用中,都有一个缺省的数据库会话 Session。通常情况下,每个数据库应用中只有这一个会话。无论是查询函数修改数据,在同一时间内只能进行其中的一件事情, 而且进行这一件事情的时候应用程序不能响应键盘、鼠标以及其它的 Windows消息。这就是在 窗口区域会显示一片空白的原因所在。当然,只要将查询或数据操纵构造成线程对象,情况会好一些,至少可以接受窗口消息,也可以随时终止查询或数据操纵,而不会在屏幕上显示出太难看的白色。不过,这只是解决了问题的一部分。假如在进行一个线程查询的时候,用户通过 按钮或菜单又发出了另一个查询的命令,这可如何是好,难道终止正在执行的数据库访问吗? 解决之道就是:多线程同步查询。

  实现多线程同步查询的基本思想是,为每一个查询组件(如TQuery组件)创建一个独占的 数据库会话,然后各自进行数据库访问。需要特别注意的是,因为Delphi中的 VCL组件大多都 不是线程安全的,所以应当在线程查询结束后再将DataSource组件与查询组件关联,从而显示 在DBGrid组件中。

  下面的例子只实现了静态的线程同步查询,即线程对象是固定的,并随窗体的创建和销毁 而创建和销毁。你可以就此进行改进,为每一个数据查询或数据操纵命令创建一个单独的线程对象,从而达到多线程同步查询的目的。

  注意:应用程序中的线程不是越多越好,因为线程将严重吞噬CPU资源,尽管看上去并不明显。谨慎创建和销毁线程将避免你的应用程序导致系统资源崩溃。

  下面的例子给出了同时进行的两个线程查询。第一次按下按钮时,线程开始执行;以后每次按下按钮时,如果线程处于挂起状态则继续执行,否则挂起线程;线程执行完毕之后将连接 DataSource,查询结果将显示在相应的DBGrid中。  回复  更多评论   

# re: 多線程查詢 2006-02-03 16:42 y

一个进程通常定义为程序的一个实例。在Win32中,进程占据4GB的地址空间。与它们在MS-DOS和16位Windows操作系统中不同,Win32进程是没有活力的。这就是说,一个Win32进程并不执行什么指令,它只是占据着4GB的地址空间,此空间中有应用程序EXE文件的代码和数据。EXE需要的任意DLL也将它们的代码和数据装入到进程的地址空间。除了地址空间,进程还占有某些资源,比如文件、动态内存分配和线程。当进程终止时,在它生命期中创建的各种资源将被清除。

正是因为进程是静态的,所以要让进程完成一些工作,进程必须至少占有一个线程,所以线程是描述进程内的执行,正是线程负责执行包含在进程的地址空间中的代码。实际上,单个进程可以包含几个线程,它们可以同时执行进程的地址空间中的代码。为了做到这一点,每个线程有自己的一组CPU寄存器和堆栈。每个进程至少有一个线程在执行其地址空间中的代码,如果没有线程执行进程地址空间中的代码,进程也就没有继续存在的理由,系统将自动清除进程及其地址空间。为了运行所有这些线程,操作系统为每个独立线程安排一些CPU时间,操作系统以轮转方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。创建一个Win32进程时,它的第一个线程称为主线程,它由系统自动生成,然后可由这个主线程生成额外的线程,这些线程又可生成更多的线程。



3 线程的编程技术

线程编程的关键技术主要包括:线程的创建、终止、优先级的设置、挂起和恢复。

3.1 创建一个线程

Windows API 调用CreateThread 函数来创建一个线程。函数如下:

HANDLE CreateThread(

LPSECURITY_ATTRIBUTES lpThreadAttributes,

//线程安全属性地址

DWORD dwStackSize,

//初始化线程堆栈尺寸

LPTHREAD_START_ROUTINE lpStartAddress,

//线程函数所指向的地址

LPVOID lpParameter,

//给线程函数传递的参数

DWORD dwCreationFlags,

//有关线程的标志

LPDWORD lpThreadId

//系统分配给线程的ID );

第一个参数是安全属性,一般设为nil,使用缺省的安全属性。当我们想此线程有另外的子进程时,可改变它的属性。

第二个参数是线程堆栈尺寸,一般设为0,表示与此应用的堆栈尺寸相同,即主线程创建的线程一样长度的堆栈。并且其长度会根据需要自动变长。

第三个参数,也是最重要的一个,是一个指向函数名的指针,但传递时很简单,只需在线程函数名前加上@就可以了。

第四个参数是你需要向线程函数传递的参数,一般是一个指向结构的指针。不需传递参数时,则这个参数设为nil。

第五个参数,传入与线程有关的一些标志,如果是CREATE_SUSPENDED,则创建一个挂起的线程,即这个线程本身已创建,它的堆栈也已创建。但这个线程不会被分配给CPU时间,只有当ResumeThread函数被调用后才能执行;当然,也可以调用SuspendThread函数再次挂起线程。要是标志为0,那么一旦建立线程,线程函数就被立即调用。一般传为0即可。

最后一个参数是系统分配给这个线程的唯一的ID标志。

3.2 终止线程

一般我们可以调用myThread从TThread类继承下来Suspend和Free方法:

TestThread:= TmyThread.Create(False,Query1);

TestThread. Suspend; //为了安全地关掉线程

TestThread. Free;

但 TmyThread类中只有Create而没有Free,动态建立类以后又忘记删除是我们常犯的错误之一,不过在这里由于我们指定了FreeOnTerminate(运行完即删除)为真,所以当Execute里的语句执行完后,TmyThread类占据的内存控件将被自动释放。

3.3 设定线程的相对优先级

当一个线程被首次创建时,它的优先级等同于它所属进程的优先级。在单个进程内可以通过调用SetThreadPriority函数改变线程的相对优先级。一个线程的优先级是相对于其所属的进程的优先级而言的。

SetThreadPriority( HANDLE hThread, int nPriority ): BOOL;

其中参数hThread是指向待修改优先级线程的句柄,nPriority可以是以下的值:

THREAD_PRIORITY_LOWEST,

THREAD_PRIORITY_BELOW_NORMAL,

THREAD_PRIORITY_NORMAL,

THREAD_PRIORITY_ABOVE_NORMAL,

THREAD_PRIORITY_HGHEST

3.4 挂起及恢复线程

我们可以调用myThread从TThread类继承下来Suspend方法挂起线程和Resume方法恢复线程:

TestThread. Suspend;

TestThread. Resume;

同时也可以通过设置Suspended属性:

TestThread. Suspended:=True; //挂起

TestThread. Suspended:=False; //恢复



  回复  更多评论   

# re: 多線程查詢 2006-02-03 16:43 y

4 多线程数据库查询

在DELPHI下进行多线程程序设计并不需要我们去学习庞大的WIN32API函数,我们可

以利用DELPHI下标准的多线程类TThread来完成我们的工作。

  TThread是一个abstract(抽象)类,也就是说,并不需要根据TThread来声明变量(而且根据TThread声明的变量也是完全无用),我们要做的是把TThread作为基类,用继承的形式来生成子类。实际上,根据TThread来写多线程应用程序是非常容易的。

下面就是一个基本的继承TThread生成的多线程类。

myThrd.Pas



  unit myThrd;

  interface

  uses

   Classes,DBTables;

  type

  TmyThreadΚ=class(TThread)

   private

   fQuery:tQuery;

   protected

   procedureExecute;override;

   public

   constructorCreate(Suspended:Boolean;Query:TQuery);

  end;

  implementation

  constructor

  TQueryThread.Create(Suspended:Boolean;Query:TQuery);

   begin

   inheritedCreate(Suspended);

   fQuery:ΚQuery;

   FreeOnTerminate:ΚTrue;

   end;

  procedureTQueryThread.Execute;

   begin

   fQuery.Open;

   end;

  end.

在上面这个简单的例子中,我们构造了一个TThread的子类TmyThread,用于在后台执行数据库查询。在该类的Create函数中,传递了两个参数Suspended和Query,其中Suspended用于控制线程的运行,如果Suspend为真,TmyThread类的线程在建立后将立即被悬挂,一直到运行了Resume方法,该线程才会继续执行,Query参数用于接受一个已经存在的Query控件(在窗体中真正的Query控件)而使它在多线程的情况下运行。Execute是最重要的过程,它是类TmyThread的执行部分,所有需要在这个多线程类中运行的语句都必须写在这个过程里。实际上构造自己的多线程类时,并不需要输入所有的这些代码,选择DELPHI的File菜单下的new选项,再选“TThreadObject”项目,DELPHI就会为你构造基本的程序模块。然后我们可以根据需要再做相应的修改。

然而还有一个问题值得我们注意,由于同一时刻可以有多个线程同时运行,那么我们还必须解决好同步的问题,如果几个多线程程序之间没有任何关联,那么它们之间也不会有任何冲突。但实际上,可能同时运行几个多线程的数据库应用程序,由于需要共享相同的数据库资源,我们还需要为Query1增加一个Tsession控件。

其实,虽然我们也许没有亲自使用过Session控件,但实际上,在所有的数据库访问时DELPHI都会自动建立一个临时的Session控件,使用完后又动态地删除掉它。在平常的数据库编程时,用不着我们亲自来操作,但在数据库多线程执行的情况下,为了不相互冲突,我们必须为每个数据库访问都定制自己的Session控件。这个步骤非常简单,我们只需要在窗体中增加一个Session控件,然后给它的属性“Sessionname”写一个任意的名字,并再在Query1的“Sessionname”中写一个相同的名字。这样我们的数据库程序就安全了。



5 总结

我们在Delphi 6.0和SQLServer2000的环境下做了实际应用的对比。我们的工作是从Excel中按一定的字段格式把数据导入SQLServer的一个数据表,该Excel表有2000条记录。刚开始我们用单线程方法进行,首先生成一个Excel的OLE对象,再从Excel表中每读取完一行就插入一条记录到SQLServer的数据表中,总耗费时间为4560ms,并且查询时整个测试软件界面无法响应用户的操作,这在实用的软件编写中是无法接收的。后来用了5个线程同时读取Excel表(每个线程读取400)和写入数据表中,只耗费了1780ms,而且可以进行其它的操作,性能达到了预期的效果。



参考文献

[1] Windows下多线程编程技术及其实现.http://www.yesky.com/20030325 /1659139_1.shtml

[2] 李维.Delphi 7高效数据库程序设计.北京:机械工业出版社

[3] 周长发.Delphi下深入Windows核心编程.

*本文得到广西留学回国人员科学基金项目(桂科回0342001)和广西大学第二批创新学分实践项目的资助

作者简介:邱律文,本科生;严毅,工程师,指导教师
  回复  更多评论   

只有注册用户登录后才能发表评论。
<2006年11月>
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

导航

统计

常用链接

留言簿(8)

随笔分类

随笔档案

收藏夹

青蛙学堂

最新评论

阅读排行榜

评论排行榜