5

私には独特の問題があります。

シリアル デバイスとの通信に使用する必要があるカスタム プロトコルは次のように機能します。標準の ASCII 省略形を使用しています。

  1. -> 私のプログラムは SOH を送信して送信を開始します
  2. -> 私のプログラムは STX+data+ETX のパケットを送信します (実際には SOH とこのパケットの両方を一度に送信します)
  3. <- デバイスは、パケットを受信したことを確認するために ACK を返します。
  4. -> 私のプログラムは EOT を送信して送信の終了を通知します

メッセージが応答を期待する要求であった場合、デバイスはデータを送り返します。方法SerialPort.DataReceivedが機能するため、受信したメッセージを手動でつなぎ合わせる必要があります。次のようになります。

  1. <- デバイスが SOH+STX+データを送信
  2. <- デバイスは ETX で終わる追加のバイトを送信します
  3. -> パッケージ全体が受信されると、私のプログラムは ACK を送信します
  4. <- デバイスが EOT を送信

これは基本的に、パケットの損失や NAK などを除いて、これら 2 つの通信方法ですが、あまり複雑にしたくありません。

私の問題は次のとおりです。完全なメッセージの送信または完全なメッセージの受信をアトミック操作にラップするにはどうすればよいですか? 私が望んでいないのは、受信したメッセージをつなぎ合わせている最中に、プログラムが新しいメッセージを送信することです。

Monitor.Enter()andを使用してみMonitor.Exit()ましたが、受信したイベントは別のスレッドで呼び出されるため、サイコロはありません。

Semaphoreまた、リソースが 1 つだけの を使用して、semaphore.WaitOne()送信または受信の開始時に呼び出しsemaphore.Release()、EOT を送信した後、デバイスから EOT を受信した後に呼び出してみました。これもなかなかうまくいきません。

これを改善する方法はありますか?

4

3 に答える 3

1

要求データ、応答データ、プロトコルをエンコード/デコードするステートマシン、タイムアウト、例外/エラーメッセージ、OnCompletion(SerialTxn thisTxn)イベント/デリゲート、およびその他の必要なもののメンバーを含む「SerialTxn」クラスを定義します。 1つのクラスへの完全なトランザクション。次に、1つを取得し、要求データなどをロードして、シリアルポートを処理する1つのスレッドにキューに入れます(BlockingCollection)。スレッドはインスタンスを取得し、そのメソッドを呼び出してデータをtxおよびrxし、完了したらOnCompletionイベント/デリゲートを呼び出します。これにより、同期および非同期トランザクションが可能になります。OnCompletionイベントは、元のスレッドが待機していることをイベント(AutoResetEvetなど)に通知するか、元のスレッドが必要なときに処理できるキューに応答をキューに戻すことができます。

ポートを処理するスレッドは1つだけなので、tx/rx操作が重複する可能性はありません。txnは完全に成功するか、適切な例外またはエラーメッセージをSerialTxnにロードします。エラーが発生した場合、またはデータをロガーやGUIなどに送信する必要がある場合は、関連するすべてのデータが1つのオブジェクトの1つの場所にあり、キューに入れたり、BeginInvokedなどの準備ができています。

長時間の操作をロックするのは好きではありません...

于 2013-02-06T15:21:24.940 に答える
1

シリアルポート経由で GSM モデムと通信するコードを書いたときに、同様の問題に直面しました。

名前空間でAutoResetEventクラスを利用することで、かなり良いソリューションを開発することができました。System.Threading基本的に、Send メソッドは ACK 信号を待つようにしました。

ここにスケルトンコードがあります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO.Ports;

namespace Sample
{
    public class SocketWrapper
    {
        #region Static Variables

        private static AutoResetEvent _SendWaitHandle = new AutoResetEvent(false);

        #endregion

        #region Member Variables

        private object _SendLockToken = new object();

        #endregion

        #region Public Methods

        public void Write(byte[] data)
        {
            Monitor.Enter(_SendLockToken);
            try
            {
                // Reset Handle
                _SendWaitHandle.Reset();

                // Send Data
                // Your Logic

                // Wait for ACK
                if (_SendWaitHandle.WaitOne(1000)) // Will wait for 1000 miliseconds
                {
                    // ACK Received
                    // Send EOT
                }
                else
                {
                    // Timeout Occurred
                    // Your Logic To Handle Timeout
                }
            }
            catch (Exception)
            {

                throw;
            }
            finally
            {
                Monitor.Exit(_SendLockToken);
            }
        }
        #endregion

        #region Private Methods

        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            // When ACK is received call SET
            _SendWaitHandle.Set();

        }
        #endregion
    }
}

注: DataReceived メソッドでは、デッドロックが発生する可能性があるため、Write メソッドを直接または間接的に呼び出さないようにしてください。TPL を使用して、または TPL を使用して、常に別のスレッドで受信データの処理を開始しますBackgroundWorker

于 2013-02-06T15:03:47.623 に答える
1

私はロックを使用します:

http://msdn.microsoft.com/en-gb/library/c5kehkcz(v=vs.71).aspx

serialLock というグローバル オブジェクトをセットアップし、これを使用して、ピースを接着するときに送信スレッドが待機する必要があることを確認できます。

受け取った

lock(serialLock)
{
//Glue data
}

すべての送信で:

lock(serialLock)
{
// send data
}

これにより、さまざまなスレッドなどの問題が分類されます。

また、ロックを解除する前に、グルー データ セクションで完全なメッセージを受信したことを確認します。おそらく、これをクリアするには、DataReceived イベントでスレッドセーフなデータ構造を更新する必要があり、グルー データ セクションでは、ロックを解除する前に、このセクションにとどまって完全なメッセージのデータ構造をチェックする必要があります。

于 2013-02-06T14:12:47.580 に答える