2

ここで最初の質問ですので、みなさん、こんにちは。

私が取り組んでいる要件は、シリアルポートを介して外部デバイスと通信する小さなテストアプリケーションです。通信には時間がかかる場合があり、デバイスはあらゆる種類のエラーを返す可能性があります。

デバイスは独自のクラスでうまく抽象化されており、GUIスレッドは独自のスレッドで実行を開始し、通常のオープン/クローズ/データの読み取り/データの書き込みの基本機能を備えています。GUIも非常にシンプルです。COMポートを選択し、開く、閉じる、デバイスからのデータの読み取りまたはエラーを表示する、変更を許可する、書き戻すなどです。

問題は、単にデバイスクラスからGUIを更新する方法ですか?デバイスが処理するデータにはいくつかの異なるタイプがあるため、GUIフォーム/スレッドクラスと動作中のデバイスクラス/スレッドの間に比較的一般的なブリッジが必要です。GUIからデバイスへの方向では、すべてが正常に機能します。[開始]さまざまなGUIで生成されたイベントで、開く/閉じる/読み取り/書き込みなどの呼び出しを呼び出します。

ここでスレッド(C#の別のスレッドからGUIを更新する方法は?)を読みました。ここでは、GUIとワーカースレッドが同じクラスにあると想定されています。グーグル検索は、デリゲートを作成する方法または古典的なバックグラウンドワーカーを作成する方法を投げ出しますが、それらはソリューションの一部であるかもしれませんが、それは私が必要とするものではありません。それで、使用できる単純だが一般的な構造はありますか?

私のC#のレベルは中程度であり、私はそれを理解する(そしてポストバックする)手がかりを与えられて、私のすべての仕事の人生をプログラミングしてきました...助けてくれてありがとう。

4

3 に答える 3

6

デバイス クラスが UI に渡す必要があるすべての情報をバックグラウンド スレッドで呼び出すことができる UI クラスのパブリック メソッドを公開できます。このパブリック メソッドはバックグラウンド スレッドのコンテキストで実行されますが、UI クラスに属しているため、これまでに説明したコール マーシャリング手法のいずれかを使用できるようになりました。

したがって、最も単純な設計は次のようになります。

  • デバイスから使用するUIにデータを渡すために使用しているデータ構造を取得するMyUIFormようなメソッドをUIクラスに追加します(たとえば)。後で DI/IoC をサポートする場合はUpdateUI()、そのメソッドをインターフェイス (たとえば ) で宣言し、フォームに実装させることができます。IUIForm
  • スレッド A (UI スレッド) では、UI クラスがデバイス クラスを作成し、必要なすべての設定を初期化し、バックグラウンド スレッドを開始します。また、それ自体へのポインターも渡します。
  • スレッド B では、デバイスがデータを収集してMyUIForm.UpdateUI()(またはIUIForm.UpdateUI()) を呼び出します。
  • UpdateUIInvokeまたは必要にBeginInvoke応じて。

すべての UI とプレゼンテーション ロジックを UI クラスにカプセル化するという副次的な利点があることに注意してください。デバイス クラスは、ハードウェアの処理に集中できるようになりました。

更新:スケーラビリティに関する懸念に対処するには -

アプリがどれだけ大きくなり、UI クラスがいくつあるかに関係なく、更新する特定の UI クラスの BeginInvoke を使用して、スレッドの境界を越える必要があります。(その UI クラスは、特定のコントロールまたは特定のビジュアル ツリーのルートである可能性がありますが、実際には問題ではありません) 主な理由は、複数の UI スレッドがある場合、UI の更新がスレッド上で確実に行われるようにする必要があるためです。この特定の UI は、Windows のメッセージングとウィンドウの動作に合わせて作成されました。したがって、境界スレッドを越える実際のロジックは、UI レイヤーにカプセル化する必要があります。

デバイス クラスは、更新が必要な UI クラスとスレッドを気にする必要はありません。実際、個人的には、デバイスが UI を完全に認識しないようにし、さまざまな UI クラスがサブスクライブできるイベントをデバイスに公開するだけです。

別の解決策は、スレッド化をデバイス クラスに完全にカプセル化し、UI がバックグラウンド スレッドの存在を認識しないようにすることです。ただし、スレッド境界の交差はデバイス クラスの責任となり、そのロジック内に含まれている必要があるため、スレッドを交差する UI の方法を使用しないでください。これは、デバイス クラスが特定の UI スレッドにバインドされていることも意味します。

于 2010-04-13T07:20:19.510 に答える
0

これは、イベント ハンドラーを持つバージョンです。
これは単純化されているため、フォームには UI コントロールがなく、SerialIoEventArgs クラスにはプロパティがありません。

  1. コメント // Update UI の下に UI を更新するコードを配置します。
  2. コメントの下にシリアル IO を読み取るコードを配置します // Read from serial IO
  3. フィールド/プロパティを SerialIoEventArgs クラスに追加し、メソッド OnReadCompleted に入力します。
public class SerialIoForm : Form
{
    private delegate void SerialIoResultHandlerDelegate(object sender, SerialIoEventArgs args);
    private readonly SerialIoReader _serialIoReader;
    private readonly SerialIoResultHandlerDelegate _serialIoResultHandler;

    public SerialIoForm()
    {
        Load += SerialIoForm_Load;
        _serialIoReader = new SerialIoReader();
        _serialIoReader.ReadCompleated += SerialIoResultHandler;
        _serialIoResultHandler = SerialIoResultHandler;
    }

    private void SerialIoForm_Load(object sender, EventArgs e)
    {
        _serialIoReader.StartReading();
    }
    private void SerialIoResultHandler(object sender, SerialIoEventArgs args)
    {
        if (InvokeRequired)
        {
            Invoke(_serialIoResultHandler, sender, args);
            return;
        }
        // Update UI
    }
}
public class SerialIoReader
{
    public EventHandler ReadCompleated;
    public void StartReading()
    {
        ThreadPool.QueueUserWorkItem(ReadWorker); 
    }
    public void ReadWorker(object obj)
    {
        // Read from serial IO

        OnReadCompleated();
    }

    private void OnReadCompleated()
    {
        var readCompleated = ReadCompleated;
        if (readCompleated == null) return;
        readCompleated(this, new SerialIoEventArgs());
    }
}

public class SerialIoEventArgs : EventArgs
{
}
于 2010-04-13T08:40:04.637 に答える
0

したがって、上記の回答に基づいていくつかの調査を行った後、Google でさらに検索し、C# について少し知っている同僚に尋ねて、問題に対する私が選択した解決策を以下に示します。私はコメント、提案、改良に引き続き関心を持っています。

最初に、問題についてさらに詳しく説明します。これは、GUI が反応しなければならない一連のイベントを通じて、GUI が何かを制御しているという意味で実際にはかなり一般的であり、完全に抽象的である必要があります。いくつかの明確な問題があります。

  1. さまざまなデータ型のイベント自体。プログラムが進化するにつれて、イベントは追加、削除、変更されます。
  2. GUI (さまざまな UserControls) を構成するいくつかのクラスと、ハードウェアを抽象化するクラスをブリッジする方法。
  3. すべてのクラスはイベントを生成および消費でき、可能な限り分離したままにする必要があります。
  4. コンパイラは、コーディング コックアップを可能な限り特定する必要があります (たとえば、あるデータ型を送信するイベントと別のデータ型を期待するコンシューマ)。

これの最初の部分はイベントです。GUI とデバイスは複数のイベントを発生させることができ、それらに関連付けられたさまざまなデータ型が存在する可能性があるため、イベント ディスパッチャーは便利です。これは、イベントとデータの両方で一般的でなければならないため、次のようになります。

    // Define a type independent class to contain event data
    public class EventArgs<T> : EventArgs
    {
    public EventArgs(T value)
    {
        m_value = value;
    }

    private T m_value;

    public T Value
    {
        get { return m_value; }
    }
}

// Create a type independent event handler to maintain a list of events.
public static class EventDispatcher<TEvent> where TEvent : new()
{
    static Dictionary<TEvent, EventHandler> Events = new Dictionary<TEvent, EventHandler>();

    // Add a new event to the list of events.
    static public void CreateEvent(TEvent Event)
    {
        Events.Add(Event, new EventHandler((s, e) => 
        {
            // Insert possible default action here, done every time the event is fired.
        }));
    }

    // Add a subscriber to the given event, the Handler will be called when the event is triggered.
    static public void Subscribe(TEvent Event, EventHandler Handler)
    {
        Events[Event] += Handler;
    }

    // Trigger the event.  Call all handlers of this event.
    static public void Fire(TEvent Event, object sender, EventArgs Data)
    {
        if (Events[Event] != null)
            Events[Event](sender, Data);

    }
}

ここで、いくつかのイベントが必要で、C の世界から来ました。私は列挙型が好きなので、GUI が発生させるいくつかのイベントを定義します。

    public enum DEVICE_ACTION_REQUEST
    {
    LoadStuffFromXMLFile,
    StoreStuffToDevice,
    VerifyStuffOnDevice,
    etc
    }

EventDispatcher の静的クラスのスコープ (通常は名前空間) 内のどこにでも、新しいディスパッチャーを定義できるようになりました。

        public void Initialize()
        {
        foreach (DEVICE_ACTION_REQUEST Action in Enum.GetValues(typeof(DEVICE_ACTION_REQUEST)))
            EventDispatcher<DEVICE_ACTION_REQUEST>.CreateEvent(Action);
        }

これにより、列挙型の各イベントのイベント ハンドラーが作成されます。

そして、消費する Device オブジェクトのコンストラクターで、次のコードのようにイベントをサブスクライブすることによって消費されます。

        public DeviceController( )
    {
        EventDispatcher<DEVICE_ACTION_REQUEST>.Subscribe(DEVICE_ACTION_REQUEST.LoadAxisDefaults, (s, e) =>
            {
                InControlThread.Invoke(this, () =>
                {
                    ReadConfigXML(s, (EventArgs<string>)e);
                });
            });
    }

InControlThread.Invoke は、単に呼び出し呼び出しをラップする抽象クラスです。

イベントは、GUI で簡単に発生させることができます。

        private void buttonLoad_Click(object sender, EventArgs e)
        {
            string Filename = @"c:\test.xml";
            EventDispatcher<DEVICE_ACTION_REQUEST>.Fire(DEVICE_ACTION_REQUEST.LoadStuffFromXMLFile, sender, new EventArgs<string>(Filename));
        }

これには、発生するイベントと消費するイベントのタイプ (ここでは文字列 Filename) が一致しない場合、コンパイラが不平を言うという利点があります。

できることはたくさんありますが、これが問題の本質です。コメントで言ったように、特に明らかな省略/バグまたは欠陥がある場合は、興味があります。これが誰かに役立つことを願っています。

于 2010-04-19T09:03:50.270 に答える