2

呼び出し元がコマンド文字列をシリアル ポート経由でハードウェア デバイスに送信できるようにするメソッドを作成するように依頼されました。コマンドを送信した後、メソッドはデバイスからの応答を待つ必要があり、デバイスからの応答が呼び出し元に返されます。

さらに複雑なことに、ハードウェア デバイスは定期的に未承諾のデータ パケットを PC に送信します (アプリがレポート用に保存する必要があるデータ)。そのため、シリアル コマンドを送信すると、コマンド応答を受信する前に 1 つまたは複数のデータ パケットを受信することがあります。

その他の考慮事項: 複数のクライアントが同時にシリアル コマンドを送信する可能性があります。これは、この方法が WCF サービスの基礎を形成するためです。また、メソッドは同期的である必要があるため (ここでは説明しません)、コールバックを使用してクライアントに応答を返すことはできません。

「複数のクライアント」に関しては、BlockingCollection<> を使用して受信コマンドをキューに入れ、一度に 1 つずつタスクを実行するバックグラウンド スレッドを使用して、シリアル ポートの競合を回避することを計画していました。

ただし、着信シリアルデータを処理する方法がわかりません。私が最初に考えたのは、シリアル ポートを継続的に読み取り、データ分析パケットを保存するだけでなく、コマンド応答も探す別のバックグラウンド スレッドを用意することでした。1つが受信されると、スレッドは何らかの方法で最初にシリアルコマンドを送信したメソッドに応答データを返します(それ以来ずっと待っていました-メソッドが同期的であるという規定があることを思い出してください)。

それは私が確信していない最後のビットです - バックグラウンドスレッドがコマンドの応答を受け取るまでメソッドを待機させるにはどうすればよいですか? また、バックグラウンド スレッドから待機中のメソッドに応答を渡して、呼び出し元に返すにはどうすればよいでしょうか? 私はスレッド化が初めてなので、これについて間違った方法で行っていますか?

前もって感謝します

アンディ

4

4 に答える 4

2

まずSerialPort、フレームワークに付属のクラスを使用すると、データ受信イベントはすでに非同期になっています。何かを送信すると、データは非同期で入ってきます。

私が試したいのは、回答を待つ必要があるすべてのリクエストをキューに入れることです。全体的な受信ハンドラーで、受信データが要求の 1 つに対する応答であるかどうかを確認します。その場合は、リクエスト情報とともに返信を保存します (そのための何らかの状態クラスを作成します)。他のすべての着信データは通常どおり処理されます。

では、リクエストが応答を待つようにするにはどうすればよいでしょうか。コマンドを送信して応答を返す呼び出しは、状態オブジェクトを作成し、それをキューに入れ、応答が受信されたかどうかを確認するためにオブジェクトを監視します。応答があった場合、呼び出しは結果を返します。

考えられる概要は次のとおりです。

string SendAndWait(string command)
{
    StateObject state = new StateObject(command);
    state.ReplyReceived = new ManualResetEvent(false);
    try
    {
        SerialPortHandler.Instance.SendRequest(command, state);
        state.ReplyReceived.WaitOne();
    }
    finally
    {
        state.ReplyReceived.Close();
    }

    return state.Reply;
}

なにSerialPortHandlerInstanceこれを、シングルトン インスタンスにアクセスするためのプロパティを含むシングルトン クラスにします。このクラスは、すべてのシリアル ポート関連の処理を行います。また、「帯域外」情報 (コマンドへの応答ではないデータ) が入ったときに発生するイベントも含める必要があります。

またSendRequest、コマンドをシリアル デバイスに送信し、状態オブジェクトを内部リストに格納し、コマンドの応答が来るのを待ち、応答で状態オブジェクトを更新するメソッドも含まれています。

状態オブジェクトには、状態オブジェクトのプロパティを変更ReplyReceivedした後に設定される、呼び出される待機ハンドルが含まれています。そうすれば、ループと. また、呼び出す代わりに、応答が来るまで待機するミリ秒数で呼び出すこともできます。このようにして、ある種のタイムアウト機能を実装できます。SerialPortHandlerReplyThread.SleepWaitOne()WaitOne(timeout)timeout

これは、次のようになりますSerialPortHandler

void HandlePossibleCommandReply(string reply)
{
    StateObject state = FindStateObjectForReply(reply);
    if (state != null)
    {
        state.Reply = reply;
        state.ReplyReceived.Set();

        m_internalStateList.Remove(state);
    }
}

注意してください:これは私が始めようとするものです。これは非常に最適化できると確信していますが、ご覧のとおり、「マルチスレッド」があまり関係していませんSendAndWait。別のクライアントがまだ応答を待っている間に複数のクライアントがコマンドを発行できるように、メソッドのみを呼び出す必要があります。

編集
別の注意: メソッドは WCF サービスの基礎を形成する必要があると言っています。これにより、サービスを正しく構成した場合、サービスへの呼び出しごとにサービスクラスのインスタンスが作成されるため、SendAndWaitメソッドがサービスの独自のインスタンスで「ライブ」になり、必要さえなくなるため、作業が簡単になります。まったく再入可能です。その場合、SerialPortHandler現在サービス クラスのインスタンスが存在するかどうかに関係なく、 が常にアクティブであること (=> が作成され、実際の WCF サービスとは独立して実行されていること) を確認する必要があります。

編集 2
コメントで提案されているように、サンプル コードをループおよびスリープしないように変更しました。

于 2012-04-18T11:55:51.807 に答える
1

バックグラウンド スレッドがコマンド応答を受け取るまで本当にブロックしたい場合は、コマンドをキューに入れ、それを返すときに、バックグラウンド スレッドがオブジェクトをロックすることを検討できます。次に、ロックを待って続行します。

// in main code:
var locker = mySerialManager.Enquee(command);
lock (locker)
{
     // this will only be executed, when mySerialManager unlocks the lock
}

// in SerialManager
public object Enqueue(object command)
{
    var locker = new Object();
    Monitor.Enter(locker); 
    // NOTE: Monitor.Exit() gets called when command result 
    // arrives on serial port
    EnqueueCommand(command, locker);
    return locker;
}
于 2012-04-18T11:53:41.467 に答える
1

いくつかのこと。それらを要求したコマンドへのシリアル応答を結び付けることができる必要があります。コマンドで出て、応答で返されるインデックスまたはシーケンス番号があると思いますか?

それを考えると、あなたは大丈夫なはずです。リクエストとレスポンスを表すには、ある種の「serialAPU」クラスが必要です。これらが何であるかはわかりません。おそらく単なる文字列です。わかりません。クラスには autoResetEvent も必要です。とにかく、「DoSerialProtocol()」関数で serialAPU を作成し、要求データをロードして、シリアル スレッドのキューに入れ、autoResetEvent を待ちます。スレッドが serialAPU を取得すると、serialAPU にインデックス/シーケンス番号を格納し、serialAPU をベクトルに格納して、要求を送信できます。

データが入ってきたら、プロトコルを処理し、データが有効な応答である場合は、データからインデックス/シーケンスを取得し、ベクトル内の serialAPU で一致する値を検索します。ベクトルから一致する serialAPU を削除し、応答データをロードして、autoResetEvent を通知します。最初に「DoSerialProtocol()」を呼び出したスレッドが実行され、応答データを処理できるようになります。

もちろん、たくさんの「うねり」があります。タイムアウトは 1 回です。CritcalSectionまたはatomicCompareandSwapで保護され、「Esubmitted」で初期化された状態の列挙型をserialAPUに入れたいと思うでしょう。元のスレッドが autoResetEvent の待機をタイムアウトすると、serialAPU の状態列挙を「EtimedOut」に設定しようとします。成功した場合は、呼び出し元にエラーを返します。同様に、シリアル スレッドで、状態が EtimedOut の serialAPU が見つかった場合は、それをコンテナーから削除するだけです。応答データと一致する serialAPU が見つかった場合、状態を「EdataRx」に変更しようとし、成功した場合。autoRestEvent を発生させます。

もう 1 つは、厄介な OOB データです。それが入ってきたら、serialAPU を作成し、OOB データをロードし、状態を 'EOOBdata' に設定し、'OOBevent' を呼び出します。

于 2012-04-18T11:55:03.390 に答える
-1

BackgroundWorker -Classをご覧になることをお勧めします

これらは、このクラスのイベント(RunWorkerCompleted)であり、ワーカーがジョブを終了したときに発生します。

于 2012-04-18T11:40:01.383 に答える