0

我想在一个单独的线程中执行长查询,以便能够中止它们并向用户提供反馈。所有这一切都有效,但我有时会遇到访问冲突,因为我认为 OnNotice 事件的处理没有以正确的方式完成,我想知道这样做的正确方式。

我在 Delphi 2010 上使用 Devart 的 PgDAC 和 OmniThreadLibrary。

我执行的 PostgreSQL 代码是一个存储过程,其中包含以下内容:

RAISE NOTICE 'ad: %',myad.name;

这是我的代码中有趣的部分:

procedure TFDecomptes.FormCreate(Sender: TObject);
begin
  ThreadConnection := TPgConnection.Create(Self);
  ThreadConnection.Assign(DM.PgConnection1);
end;

ThreadConnection是将用于执行查询的 TPgConnection(在单独的线程中)。

procedure TFDecomptes.BInterruptClick(Sender: TObject);
begin
  ThreadConnection.BreakExec;
end;

这就是“中断查询”按钮的作用。我不确定这是否非常“线程安全”,因为它在主线程中使用,但在专用于查询执行线程的 TPgConnection 上做了一些事情。

procedure TFDecomptes.OmniEventMonitor1TaskMessage(const task: IOmniTaskControl; const msg: TOmniMessage);
begin
  case msg.MsgID of
    1: begin
         CalculationError:=msg.MsgData.AsString;
       end;
  end;
end;

这是我显示线程执行期间发生的错误(如 SQL 错误或查询取消)的地方。

procedure TFDecomptes.PgConnectionNotice(Sender: TObject; Errors: TPgErrors);
var s:String;
begin
  s:=Errors[Errors.Count-1].ToString;
  if copy(s,1,4)='ad: ' then begin
    delete(s,1,4);
    LAD.Caption:=s;
  end;
end;

这是 OnNotice 事件处理。它所做的只是修改标签的标题。

procedure InternalExecQuery(const task: IOmniTask);
Var q:TPgSQL;
begin
  q:=Task.Param['pgsql'];
  Try
    q.Execute;
  Except
    On E:Exception do task.Comm.Send(1,e.Message);
  End;
end;

procedure TFDecomptes.StartClick(Sender: TObject);
begin
  ThreadConnection.OnNotice:=PgConnectionNotice;
  Timer1.Enabled:=True;
  CalculationTask := CreateTask(InternalExecQuery, 'CalculeDecomptes')
    .MonitorWith(OmniEventMonitor1)
    .SetParameter('pgsql', PgSQL)
    .Run;
end;

这就是查询的运行方式。

所以PgConnectionNotice事件(在主线程中运行)附加到ThreadConnection(在查询执行线程中使用),这就是我怀疑生成这些随机访问冲突的原因。

我不知道如何处理这个。lock我应该在 PgConnectionNotice中使用某种when (同步?)。

这是我尝试过的:

procedure TFDecomptes.OmniEventMonitor1TaskMessage(const task: IOmniTaskControl; const msg: TOmniMessage);
begin
  case msg.MsgID of
    1: begin
         CalculationError:=msg.MsgData.AsString;
       end;

    2: begin
         lad.caption:='Here';
       end;
  end;
end;

procedure TFDecomptes.PgConnectionNotice(Sender: TObject; Errors: TPgErrors);
begin
  // I am not using the passed string in this test
  CalculationTask.Comm.Send(2,Errors[Errors.Count-1].ToString);
end;

在 PgConnectionNotice(MsgId=2)中发送的消息永远不会被OmniEventMonitor1TaskMessage.

我曾尝试使用CalculationTask.Invoke但不明白如何调用它以传递字符串参数(我认为 Delphi 2010 不允许匿名函数)。

当我尝试像这样取消查询的更简单操作时,它停止取消查询:

procedure TFDecomptes.DoTheInterrupt;
begin
  ThreadConnection.BreakExec;
end;

procedure TFDecomptes.BInterruptClick(Sender: TObject);
begin
  CalculationTask.Invoke(DoTheInterrupt);
end;

所以我想我不应该通过CalculationTask. 我应该将创建的任务存储在InternalExecQuery全局变量中并使用它吗?

4

1 回答 1

0

主要问题是我很困惑IOmniTask并且IOmniTaskControl. IOmniTask是用于向主线程发送消息的后台接口,而IOmniTaskControl是主线程的接口,用于与后台任务对话。

所以在里面使用 CalculationTask (它是 a IOmniTaskControlPgConnectionNotice是一个双重错误:因为PgConnectionNotice是从后台线程中触发的,所以我使用主线程的变量从后台线程向后台线程发送消息。

所以我添加了一个名为的全局变量RunningTask

Var RunningTask : IOmniTask;

将其设置为nil表单OnCreate并修改任务的代码,如下所示:

procedure InternalExecQuery(const task: IOmniTask);
Var q:TPgSQL;
begin
  RunningTask := task;
  try
    q:=Task.Param['pgsql'];
    Try
      q.Execute;
    Except
      On E:Exception do task.Comm.Send(1,e.Message);
    End;
  finally
    RunningTask := Nil;
  end;
end;

事件OnNotice现在看起来像:

procedure TFDecomptes.PgConnectionNotice(Sender: TObject; Errors: TPgErrors);
begin
  if RunningTask=Nil then
    // do nothing, old, pending notices
  else
    RunningTask.Comm.Send(2,Errors[Errors.Count-1].ToString);
end;

我知道定义一个全局变量是不干净的,尽管我知道最多会有一个后台任务。我可能应该将IOmniTask引用存储在其中,ThreadConnection因为这SenderPgConnectionNotice.

于 2016-05-30T18:18:55.460 回答