2016-05-13 5 views
0

長いクエリを別のスレッドで実行して、それらを中止したり、ユーザーにフィードバックを与えたりしたい。これはすべて動作していますが、OnNoticeイベントの処理が正しい方法で行われていないため、アクセス違反が発生することがあります。これを行う正しい方法を知りたいと思います。私はデルファイ2010年にPostgreSQLのイベントを、DevartのPgDACコンポーネントを使用してDelphiの別のスレッドに処理する

私は実行PostgreSQLのコードをDevartのPgDACとOmniThreadLibraryを使用しています

であるようなものが含まれ、ストアドプロシージャ:ここ

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; 

これがクエリの実行方法です。

ThreadConnection(クエリ実行スレッドで使用される)にPgConnectionNoticeイベント(メインスレッドで実行中)が添付されています。これがこれらのランダムアクセス違反を生成すると思われます。

私はこれをどう対処するか分かりません。私はPgConnectionNotice(Synchronize?)の内部で何らかのlockを使うべきですか?

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; 

(のMsgId = 2)PgConnectionNoticeに送信されたメッセージをOmniEventMonitor1TaskMessageによって受信されることはない:

これは私が試したものです。

私はCalculationTask.Invokeを使用しようとしましたが、文字列パラメータを渡すためにそれを呼び出す方法がわかりませんでした(Delphi 2010では匿名関数が許されていないと思います)。私はこのようなクエリをキャンセルする単純な行動をしようとしたとき

、それがクエリをキャンセル停止:

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

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

だから私は、私はCalculationTaskを経由して通話を行うべきではないと思います。 InternalExecQueryで作成したタスクをグローバル変数に格納して使用する必要がありますか?

+0

通知イベント内で 'GetCurrentThreadId()'をチェックしてください。メイン(UI)スレッドIDと異なる場合は、synchronize()が必要です... – whosrdaddy

+0

ありがとうございます。同期する**。また、私はOTLとSynchronizeを使うことができないと思います。同期はTThreadメソッドです。 –

+0

あなたはTThread.Synchronize()を使用することができます。静的メソッドです.TThreadクラスにする必要はありません。パラメータとして匿名メソッドを使用できます。 – whosrdaddy

答えて

0

主な問題は、私がIOmniTaskIOmniTaskControlを混同していたことです。 IOmniTaskは、メインスレッドにメッセージを送信するために使用するバックグラウンドインターフェイスです。IOmniTaskControlは、メインスレッドのインターフェイスで、バックグラウンドタスクとの会話に使用されます。だから、PgConnectionNoticeの内側(IOmniTaskControlです)CalculationTaskを使用して

は、二重のミスです: PgConnectionNoticeは、バックグラウンドスレッドの中から発射されているので、私は、メインスレッドの変数を使用して、バックグラウンドスレッドから、バックグラウンドスレッドにメッセージを送信していました。

だから私はRunningTaskという名前のグローバル変数を追加:フォームのOnCreatenilに設定し、それを

Var RunningTask : IOmniTask; 

をし、このようなタスクのコード変更:

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; 

グローバル変数を定義するのはきれいではないことは知っていますが、多くとも1つのバックグラウンドタスクがあることはわかっています。これはSenderPgConnectionNoticeにあるため、の参照をThreadConnectionに保存したはずです。

関連する問題