0

SerialPort を使用してハードウェアと接続する必要がある C# マルチスレッド アプリケーションがあります。

プログラムはほとんどがコマンド応答シーケンスですが、内部エラーが原因でハードウェアが未承諾の「RESET」メッセージを送信する可能性があり、その時点でソフトウェアは特定の値を設定する一連のコマンドを送信してプログラムを再初期化する必要があります。

複数のスレッド (スレッドプールから) が TakeSampleNow() を実行しようとする可能性があります

public class ALComm
{
    private readonly AutoLoaderManager _manager;
    private readonly AutoResetEvent dataArrived = new AutoResetEvent(false);
    private SerialPort _alPort;
    private string _alResponse;

   .... Code to init _alPort and attach datareceived event etc

   public void TakeSampleNow()
    {
        if (Monitor.TryEnter(_alPort, 1000)) //let's wait a second
        {
            _manager.MessageList.Enqueue("Try sampling");
            try
            {
               Send("Command1");
               string response = Receive();

               switch(response)
                {
                   case "X": blah blah..
                   case "Y": blah blah..
                } 

               Send("Command2");
               string response = Receive();

               while(response != "OK")
                 {
                   Send("Command3");
                   string response = Receive();
                   Send("Command2");
                   string response = Receive();
                 }
            }
            finally
            {
                Console.WriteLine("Releasing port");
                //Thread.CurrentThread.Priority = ThreadPriority.Normal;
                Monitor.Exit(_alPort);
            }
        else
        {
            _manager.MessageList.Enqueue("Port is busy!!!");
        }
   }

    public string Receive()
    {
        string inString = null;

            dataArrived.WaitOne(1000);
            inString = _alResponse;

        return inString;
    }

    private void AlPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        _alResponse = _alPort.ReadLine();

        //if (_alResponse.ToUpper().Contains("RESET"))
        //{
        //    _alState = AlState.Reset;
        //    TryInitialize();
        //}

        dataArrived.Set();            
    }

    private void TryInitialize()
    {
        Monitor.Enter(_alPort);       //lock so other threads do not access samplenow during initialization
        try
        {
            string response;

            Console.WriteLine("Initializing ... The AutoLoader");
            _alPort.DiscardInBuffer();

            Send("CommandX");
            response = Receive();

            --- blah blah

           _alResponse = "";
        }
        finally
        {
            Monitor.Exit(_alPort);
        }
    }

datareceived イベントで応答を確認し、TryInitialize() でロックを待機し、TakeSampleNow の他のスレッドがロックを解放するのを待つことができます。_alResponse に「RESET」が含まれているかどうか、およびメソッドから返されるかどうかを各応答で確認する必要があります。 . それはそれをより複雑にします。

どうすればこれを改善できるかについての提案。ステート マシンになる可能性はあると思いますが、概念化することはできません。

4

2 に答える 2

1

プロトコルの詳細をあまり提供していません-コマンド/応答のペアが重複する可能性があるかどうか、もしそうであれば、応答がコマンドとどのように一致するかについては言いません。

状態エンジンでこれを行うことができるはずです。BlockingCollection でイベントを待機する独自のスレッドでステートマシンを実行します。プロトコルを実行し、着信バイトをメッセージに解析するには、「SerialRecv」スレッドも必要です。

SM キューにイベントを運ぶために、'SerialEvent' クラスを 1 つだけ使用します。クラスには、rx バッファ、txData、解析されたデータ、tx 文字列を組み立てるためのデータ、例外/errorMess フィールドのイベントとメンバーを記述する列挙型が必要です。完了した要求/応答をディスプレイまたはロガーに転送します)。

すぐに思いつくいくつかのイベント: EsmNewRequestResponse、EsmRxData、EsmResetRx

イベント enum は、いくつかの段階として、SM によって使用されない他の値を持つ場合があります (例: EsmError、EsmLog、EsmDisplay)。

タイムアウトが必要な場合は、SM 入力キューで take() をタイムアウトすることによって生成できます。

はい、省略したものがあります。

複数のスレッドが SerialEvent インスタンスを「一度に」発行すると、SM は最初のインスタンスを処理している間に新しい SerialEvent を取得します。SM には、処理待ちの SerialEvents を保持するための別のキュー/キューが必要です。BlockingCollection/スレッドによる SM のシリアル化により、この「保留中」のキューはスレッドセーフである必要はありません。SM は、要求/応答が完了した後にこの保留中のキューをチェックして、処理する別のキューがあるかどうかを確認する必要があります。

複数のスレッドからの要求/応答を同期的に処理するには、要求元のスレッドに何らかの待機が必要です。SerialEvent クラスの AutoResetEvent で十分です。SerialEvent をシステムに送信すると、SerialEvent インスタンスがキューに入れられ、AutoResetEvent を待機します。インスタンスの処理が完了すると (つまり、応答の受信、エラー、またはタイムアウト)、SM はイベントを設定し、元のスレッドはその SerialEvent インスタンスにデータが入力された状態で実行されます。

次に、SerialEvent クラスは、CG を継続的に作成するよりも、プールした方がよい可能性があるポイントに向かっています。これには、プールとして機能する別の BlockingCollection が必要です。

于 2012-05-07T21:57:11.313 に答える
1

複数のスレッドがシリアル ポートを読み取ろうとするのは望ましくありません。ポートを読み取るだけの単一のスレッドが必要です。データを取得すると、複数のサンプル スレッドで処理できるキューまたは同様のデータ構造にメッセージを入れます。このようにして、単一のリーダー スレッドが RESET メッセージを確実に見つけて反応することができます。

于 2012-05-07T21:37:55.030 に答える