【问题标题】:DB-connection in separate thread - what's the best way?单独线程中的数据库连接 - 最好的方法是什么?
【发布时间】:2010-02-18 21:29:19
【问题描述】:

我正在创建一个访问数据库的应用程序。在每次访问数据库时,应用程序都会等待作业完成。 为了保持 UI 响应,我想将所有数据库内容放在一个单独的线程中。
这是我的想法:

  • db-thread 在创建时创建它需要的所有数据库组件
  • 现在线程只是坐在那里等待命令
  • 如果收到命令,它会执行操作并返回空闲状态。在此期间,主线程等待。
  • 只要应用程序运行,db-thread 就会一直存在

这听起来好吗?
将数据库结果从 db-thread 获取到主线程的最佳方法是什么?
到目前为止,我还没有对线程做太多事情,因此我想知道 db-thread 是否可以创建一个查询组件,主线程从中读取结果。主线程和数据库线程永远不会同时访问查询。这还会导致问题吗?

【问题讨论】:

    标签: multithreading delphi delphi-2009


    【解决方案1】:

    您正在寻找的是标准数据访问技术,称为异步查询执行。一些数据访问组件以易于使用的方式实现此功能。至少 dbGo (ADO) 和 AnyDAC 实现了这一点。让我们考虑一下 dbGo。

    这个想法很简单——你可以调用方便的数据集方法,比如 Open。该方法在后台线程中启动所需的任务并立即返回。当任务完成时,将触发一个适当的事件,通知应用程序任务完成。

    DB GUI 应用程序和 Open 方法的标准方法如下(草案):

    • 将 eoAsyncExecute、eoAsyncFetch、eoAsyncFetchNonBlock 包含到数据集 ExecuteOptions 中;
    • 从数据集断开 TDataSource.DataSet;
    • 将数据集 OnFetchComplete 设置为 proc P;
    • 显示“您好!我们正在努力处理您的请求。请稍候...”对话框;
    • 调用数据集Open方法;
    • 当查询执行完成时,将调用 OnFetchComplete,因此 P 将隐藏“等待”对话框并将 TDataSource.DataSet 连接回数据集。

    此外,您的“等待”对话框可能有一个取消按钮,用户可以使用它来取消运行时间过长的查询。

    【讨论】:

      【解决方案2】:

      首先 - 如果您对多线程没有太多经验,请不要从 VCL 类开始。使用OmniThreadLibrary,出于(除其他外)这些原因:

      • 您的抽象级别是任务,而不是线程,这是一种更好的并发处理方式。
      • 您可以在自己的线程中执行任务和使用线程池调度它们之间轻松切换。
      • 线程关闭、双向通信等所有低级细节都由您处理。你可以专注于数据库的东西。

      db-thread 在创建时创建它需要的所有数据库组件

      这可能不是最好的方法。我通常只在需要时才创建组件,但不会立即销毁。您绝对应该在线程池线程中保持连接打开,并且仅在线程处于非活动状态一段时间并且池处理它时才关闭它。但保留事务和语句对象的缓存通常也是一个好主意。

      如果它收到命令,它会执行操作并返回空闲状态。在此期间主线程等待。

      使用 OTL 时,第一部分处理得很好。但是 - 不要让主线程等待,这与首先在 VCL 线程中直接执行数据库访问相比几乎没有优势。您需要一个异步设计来充分利用多个线程。考虑一个标准的数据库浏览器表单,它具有过滤记录的控件。每次其中一个控件发生更改时,我都会通过(重新)启动计时器来处理此问题。一旦用户完成编辑,计时器事件就会触发(比如在 500 毫秒之后),并且会启动一个任务来执行根据过滤条件获取数据的语句。网格内容被清除,只有在任务完成后才会重新填充。但这可能需要一些时间,因此 VCL 线程不会等待任务完成。相反,用户甚至可以再次更改过滤条件,在这种情况下,当前任务被取消并开始新的任务。 OTL给你一个任务完成的事件,所以异步设计很容易实现。

      将数据库结果从 db-thread 获取到主线程的最佳方法是什么?

      我通常不会将数据感知组件用于多线程数据库应用程序,而是使用作为业务对象视图的标准控件。在数据库任务中,我创建这些对象,将它们放入列表中,然后任务完成事件将列表传输到 VCL 线程。

      主线程和数据库线程永远不会同时访问查询。

      对于所有按需加载数据的组件,您无法确定这一点。通常只从数据库中获取第一条记录,并在它们被消耗后继续获取。这样的组件显然不能被线程共享。

      【讨论】:

      【解决方案3】:

      我已经实现了两种策略:线程池和临时线程创建。

      我建议从创建临时线程开始,它更易于实现且更易于扩展。

      只有在(经过仔细评估后)(1)投入大量资源(和时间)来创建线程并且(2)您有很多创建请求时才移动到线程池。

      在这两种情况下,您都必须处理传递参数并收集结果。我建议使用允许此数据传递的属性来扩展线程类。

      请参阅线程使用的类、组件和函数的文档,以确保它们是线程安全的,也就是说,它们可以在不同的线程中同时使用。如果没有,您将需要同步访问。在某些情况下,您可能会发现有关线程安全的细微差别。例如,请参阅 DateTimeToStr。

      【讨论】:

        【解决方案4】:

        如果您在开始时创建线程并在以后需要时重用它,则必须确保每次“处理”时断开数据库组件(网格..)与底层数据源(disableControls)的连接数据。

        为了简单起见,我将继承 TThread 并在我自己的类中实现所有业务逻辑。结果数据集将是此类的成员,我会将其与 db 感知组合连接到同步。

        无论如何,将尽可能多的工作委派给数据库服务器并保持 UI 尽可能轻量也是非常重要的。 Firebird 是我最喜欢的数据库服务器:触发器,用于选择,用 Delphi 开发的自定义 UDF dll,许多线程安全的数据库组件,有很多示例和良好的支持(论坛):jvUIB...

        祝你好运

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-04-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-03-20
          • 1970-01-01
          相关资源
          最近更新 更多