3

中断することなく完全に実行し、結果を返さなければならない関数があります。実行中に非同期イベントによって再度呼び出される場合、最初の呼び出しが完了するまで、何らかの方法でその呼び出しをブロックする必要があります。

ミューテックス?他の何か?


[更新] 関数は私のメイン フォーム クラスにあり、クラスの 2 つのメソッドから呼び出されます。1 つは serail ポートから読み取られたデータを処理し、もう 1 つはタイマーの有効期限を処理します。これらは両方とも、一方からの関数の呼び出しが他方からの呼び出しによって中断される可能性があるため、独自のスレッドで実行されているように見えます (私にとって、アプリケーション レベルでは、設計時にメイン フォームにドロップしたコンポーネントにすぎません)。 )。

たぶんTCriticalSection?(しかし、グーグルでは、使用する必要があるのか​​、それとも 'enter/leave' を使用する必要があるのか​​が不明でありacquire/release、コードはメインフォームの単純な関数であるため、再入可能になるようです)。

おそらく、私が求めているのは、コードを「再入不可」にして、最初のエントリが完了するまでブロックする方法ですか? 答えが何であれ、私はコード例、または UTl to one が必要になると思います :-(

(このページには多くの有用な情報が記載されていることに注意してください (私はすべてを理解しているわけではありませんが、他の人にとっては非常に役立つようです))


[更新] これは GUI の更新とは関係ありません。TCOP データを送信する各イベントが応答を受信する必要があるだけです。

アプリには GUI がありますが (フォーム ベースです)、これはデバッグ情報を表示したい場合に限られます。これは、アプリがモニターのない PC で実行されるためです (はい、まだ GUI があることは知っていますが、私の問題/ポイントではありません)

4

4 に答える 4

7

非同期イベントがない別のワーカー スレッドに関数を移動します。

メイン スレッドの非同期イベントは何らかのメッセージを何らかのキューにポストする必要があるため、ワーカー スレッドの実行が完了すると、メイン スレッドは新しいパラメーターでもう一度開始します。


いくつかの詳細情報を含む更新がありました。関数が RS232 を介して受信したデータを介して動作するか、または何も動作しないと仮定すると (推測)、GUI 更新のようなことを行うと、次のアプローチをスケッチします。

  1. フォームの Windows メッセージング キューをアラートに使用します。
  2. データの受け渡しには、インターロックされたキュー オブジェクトを使用します。(はい、Windows メッセージにポインターを入れることができることは知っていますが、もう少しタイプ セーフが必要です)
  3. 長い処理には外部作業スレッドを使用します。

詳細:

  1. WM_USER の VCL ソースを grep し、パターンを確認します。Work_Complete、Work_Request、Work_EmptyQueue の 3 つのメッセージ ID 定数を宣言します。そして、対応するメッセージメソッドが形になっています。
  2. OmniThreadLibrary からのスレッドセーフなキューを使用します。ただし、System.Contnrs.TQueue または System.Generics.Collections.TQueue をサブクラス化して、それらのすべてのデータ受け渡しメソッドをクリティカル セクションにラップすることができます。全体として、OmniThreadLibrary パイプラインが適している場合は、それを参照することをお勧めします
  3. これは、ハードウェア データ作業アプリの標準的な方法です。それはMS-DOSデバイスドライバーです。またはいくつかの組み込みデバイス。高速で無駄のないデータ保存を長時間の作業から分離する必要があります。

したがって、RS232 イベント ハンドラは COM からデータを読み取り、それを TBytes に入れ、そのパケットを入力 TQueue に追加します。もう少し複雑なアプローチは、Queue に COM からのデータが既に含まれているかどうかを確認し、新しいパケットを個別にパットするのではなく、新しいパケットを古いものと集約することです。それにはより慎重なロックが必要になるため、ここに集約することはおそらくろうそくの価値がありません

タイマーは、空のパケット (長さゼロのバイト配列) を作成し、同様にキューに入れます。そのタイマーにも渡すデータがある場合、それはバリアントレコードまたは個別の入力キューなどである必要があります。しかし、情報がなければ、タイマーは情報を送信しないようですが、アラート自体を送信します

あなたの言葉によると、Timer イベントと RS232 イベントは別々のスレッドで動作します。私はそれについて疑問を持っていますが、私はあなたを信頼しなければなりません。

ワークロードをエンキューした後 (入力キューの通知イベントなど)、win32 PostMessage(MainForm.Handle, work_request) を実行します。結局、スレッド制御を 1 か所に集中させたいと考えています。スレッドを分離した状態に保つには、メッセージを投稿する必要があります。

フォームの work_request ハンドラのメイン スレッドで、入力キューがまだ空でないかどうかを調べます。そうでない場合は、ワーカー スレッドの状態を確認します。中断された場合は、再開します。

ワーカー スレッドは、入力キューに何かがあるように見えます。

  1. パケットをキューからローカル変数に抽出します
  2. 潜在的に長い処理を行う
  3. 「作業結果」パケットを出力します。これは、キーと値のペアのコレクション、または入力されたデータ フィールドと無視されるデータ フィールドのセットを含むデータ フィールドのレコードである可能性がある GUI 更新であると仮定します。
  4. そのパケットを出力キューにエンキューします (キューに入れ、Work_Complete メッセージをメイン フォームにポストします)。
  5. 1 にループします。

入力キューが空の場合、関数は終了し、スレッドは PostMessage Work_EmptyQueue を実行し、後でより多くの作業を行うか解放するために目覚めるまで自身を一時停止します。

MainForm (再びメイン スレッド) が Work_Complete を受け取ると、

  1. 出力キューからすべてのパケットをローカル変数に抽出します
  2. それらをマージします。(p1=(label1='aaaa', label2='bbbb') と p2=(label3='cccc', label1='dddd') がある場合、累積タスクは (label1='dddd', label2 ='bbbb', label3='cccc');
  3. それらを適用します(実際にはGUIを更新します)

出力キューが空の場合、手順 2 と 3 は省略されます (前の手順でマージが発生した場合は省略されます)。でも4位ではない。ステップ 1 と 2 は別です。キュー操作はスレッド連動であるため、ステップ 1 の目標はデータをできるだけ速く抽出することです。マージンは現地で行う必要があります。

MainForm が Work_EmptyQueue を受け取ると、入力キューが空でないかどうかを確認し、ワーカー スレッドを再開する可能性があります。オプションで、ステータスの GUI 更新などを行うこともできます。

それがラフスケッチです。それはあなたに特定の引用をよりよく刻むことができます。

于 2012-08-15T13:48:22.883 に答える
7

同じスレッドからの再入可能性を懸念していると仮定すると、再入可能呼び出しをブロックすると、古典的なデッドロックが発生します。これがシナリオである場合は、次のいずれかを実行する必要があります。

  1. 再入可能な呼び出しが発生しないことを確認するか、または
  2. 再入可能な呼び出しを検出し、後で処理するためにキューに追加して延期する、または
  3. 再入可能な呼び出しを検出し、単純に無視します。

おそらく、これらのオプションのどれもあなたにとって魅力的ではありません!

異なるスレッドからの同時呼び出しが心配な場合は、何らかのロックを使用できます。たとえば、Windows では通常、クリティカル セクションを使用します。

于 2012-08-15T13:49:00.837 に答える
5

次の 2 つのメカニズムを組み合わせて使用​​します。

  1. TCriticalSection を使用して、別のスレッドによるエントリから保護します。明らかに、これは必要なマルチスレッド アプリケーションのみです。
  2. 再入場ゲートをご利用ください。これは、同じスレッドからの再エントリを防ぐ単純なブール値です。保護しようとしている機能がすでに入力されている場合は、ブロックせずにバウンスします。「バウンス」とは、後で実行するために必要なアクティビティをキューに入れるなど、適切なアクションを実行することを意味します。
于 2012-08-15T13:48:38.897 に答える
1

もう1つのアプローチは、イベントソースの1つ(シリアルポートハンドラーなど)から重要な機能を入力するときに、呼び出しの間、他のイベントソース(有効期限タイマー)を無効にすることです。呼び出し後に再度有効にします。タイマーが無効になっているため、通話中に他のイベントソースを起動できないことが保証されます。

これは、シリアルデータパケットを処理しようとしているときに有効期限タイマーをオフにする場合には、非常に簡単です。有効期限タイマーが、シリアルポートから一定期間何も受信されていないことを追跡することであると想定する場合、受信したシリアルデータで有効期限タイマーを無効にするのが適切です。シリアルデータパケットの処理が終了したら、有効期限タイマーを復元/再起動します。

逆の場合(有効期限タイマーの処理中にシリアルデータを受信する)は、シリアルデータレシーバーを無効にしてデータを失うリスクを冒したくないため、少し注意が必要な場合があります。シリアルデータハンドラーがデータを失うことなく簡単に一時停止/再開できる場合は、これで完了です。それ以外の場合、ここでの最善の策は、オブジェクトインスタンスでブールフラグを使用して、重要な関数がいつ実行されているかを示すことです。

シリアルデータパケットが着信し、そのフラグがtrueの場合は、シリアルデータパケットをリストにプッシュして後で処理し、すぐに返します。有効期限タイマーハンドラーを設定して、クリティカルコールから戻る直前と直後にそのリストを確認します。リストが空でない場合は、リスト内のすべてのアイテムを処理します。有効期限タイマーハンドラーに入るときに処理されるのを待機しているデータパケットがリストにある場合は、有効期限タイマーの有効期限を無視して、チャグを続けます。

シリアルデータハンドラーで、重要な関数を呼び出す前後にリストが空でないかどうかを確認します。前にチェックすることで、シリアルデータパケットが受信された順序で処理されていることを確認できます。後でチェックすると、現在のデータパケットの処理中に到着する後続のシリアルデータが失われないことが保証されます。

シリアルデータイベントハンドラーが別のスレッドで呼び出される可能性がある場合は、ブールフラグとリストの更新をスレッドセーフな方法で慎重に処理する必要があります。シリアルイベントハンドラー(および有効期限タイマーハンドラー)がUIスレッドで実行されることが保証されている場合は、これについて心配する必要はありません。

于 2012-08-16T17:37:57.483 に答える