7

.Net2.0で非ビジュアルコンポーネントを構築しています。このコンポーネントは、非同期ソケット(BeginReceive、EndReceiveなど)を使用します。非同期コールバックは、ランタイムによって作成されたワーカースレッドのコンテキストで呼び出されます。コンポーネントユーザーはマルチスレッドについて心配する必要はありません(これが主な目標であり、私が望むものです)

コンポーネントユーザーは、任意のスレッドで非ビジュアルコンポーネントを作成できます(UIスレッドは、単純なアプリケーションの一般的なスレッドです。より深刻なアプリケーションでは、任意のワーカースレッド内にコンポーネントを作成できます)。コンポーネントは、「SessionConnected」や「DataAvailable」などのイベントをトリガーします。

問題:非同期コールバックとそこで発生するイベントのために、イベントハンドラーはワーカースレッドコンテキストで実行されます。最初にコンポーネントを作成したスレッドのコンテキストでイベントハンドラーを強制的に実行する中間レイヤーを使用したいと思います。

サンプルコード(例外処理などから削除...)

    /// <summary>
    /// Occurs when the connection is ended
    /// </summary>
    /// <param name="ar">The IAsyncResult to read the information from</param>
    private void EndConnect(IAsyncResult ar)
    {
        // pass connection status with event
        this.Socket.EndConnect(ar);

        this.Stream = new NetworkStream(this.Socket);

        // -- FIRE CONNECTED EVENT HERE --

        // Setup Receive Callback
        this.Receive();
    }


    /// <summary>
    /// Occurs when data receive is done; when 0 bytes were received we can assume the connection was closed so we should disconnect
    /// </summary>
    /// <param name="ar">The IAsyncResult that was used by BeginRead</param>
    private void EndReceive(IAsyncResult ar)
    {
        int nBytes;
        nBytes = this.Stream.EndRead(ar);
        if (nBytes > 0)
        {
            // -- FIRE RECEIVED DATA EVENT HERE --

            // Setup next Receive Callback
            if (this.Connected)
                this.Receive();
        }
        else
        {
            this.Disconnect();
        }
    }

非同期ソケットの性質上、コンポーネントを使用するすべてのアプリケーションには「If(this.InvokeRequired){...」が散らばっています。必要なのは、ユーザーがコンポーネントをドロップのようなものとして安心して使用できるようにすることだけです。 -の。

では、ユーザーがInvokeRequiredをチェックする必要なしに、イベントを発生させるにはどうすればよいでしょうか(言い換えると、最初にイベントを開始したスレッドと同じスレッドで発生したイベントを強制するにはどうすればよいですか)。

AsyncOperation、BackgroundWorkers、SynchronizingObjects、AsyncCallbacks、その他たくさんのことを読んだことがありますが、それはすべて私の頭を回転させます。

私はこれを思いついた、確かに不器用な「解決策」ですが、状況によっては失敗するようです(たとえば、静的クラスを介してWinFormsプロジェクトからコンポーネントが呼び出された場合)

    /// <summary>
    /// Raises an event, ensuring BeginInvoke is called for controls that require invoke
    /// </summary>
    /// <param name="eventDelegate"></param>
    /// <param name="args"></param>
    /// <remarks>http://www.eggheadcafe.com/articles/20060727.asp</remarks>
    protected void RaiseEvent(Delegate eventDelegate, object[] args)
    {
        if (eventDelegate != null)
        {
            try
            {
                Control ed = eventDelegate.Target as Control;
                if ((ed != null) && (ed.InvokeRequired))
                    ed.Invoke(eventDelegate, args);
                else
                    eventDelegate.DynamicInvoke(args);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.GetType());
                Console.WriteLine(ex.Message);
                //Swallow
            }
        }
    }

どんな助けでもいただければ幸いです。前もって感謝します!

編集:このスレッドによると、私の最善の策はSyncrhonizationContext.Postを使用することですが、それを自分の状況に適用する方法がわかりません。

4

4 に答える 4

2

Ok; だから、これが私がもう少し読んだ後に終わったものです:

public class MyComponent {
    private AsyncOperation _asyncOperation;

    /// Constructor of my component:
    MyComponent() {
        _asyncOperation = AsyncOperationManager.CreateOperation(null);
    }

    /// <summary>
    /// Raises an event, ensuring the correct context
    /// </summary>
    /// <param name="eventDelegate"></param>
    /// <param name="args"></param>
    protected void RaiseEvent(Delegate eventDelegate, object[] args)
    {
        if (eventDelegate != null)
        {
            _asyncOperation.Post(new System.Threading.SendOrPostCallback(
                delegate(object argobj)
                {
                    eventDelegate.DynamicInvoke(argobj as object[]);
                }), args);
        }
    }
}

ここに投稿された他の解決策は、進行中の作業のようなものでした。ここに投稿された解決策は(MSDNによると)これまでのところ最高のようです。提案は大歓迎です。

于 2010-01-08T13:54:52.887 に答える
1

私は自分の解決策を見つけたようです:

    private SynchronizationContext _currentcontext

    /// Constructor of my component:
    MyComponent() {
        _currentcontext = WindowsFormsSynchronizationContext.Current;
       //...or...?
        _currentcontext = SynchronizationContext.Current;
    }

    /// <summary>
    /// Raises an event, ensuring the correct context
    /// </summary>
    /// <param name="eventDelegate"></param>
    /// <param name="args"></param>
    protected void RaiseEvent(Delegate eventDelegate, object[] args)
    {
        if (eventDelegate != null)
        {
            if (_currentcontext != null)
                _currentcontext.Post(new System.Threading.SendOrPostCallback(
                    delegate(object a)
                    {
                        eventDelegate.DynamicInvoke(a as object[]);
                    }), args);
            else
                eventDelegate.DynamicInvoke(args);
        }
    }

私はまだこれをテストしていますが、うまくいくようです。

于 2010-01-07T17:42:20.023 に答える
0

問題を理解していないのかもしれませんが、非同期状態のカスタム オブジェクトへの参照を渡すことができるように思えます。

説明のために次の例をまとめました。

まず Callback オブジェクトがあります。これには 2 つのプロパティがあります。アクションをディスパッチする Control と呼び出す Action です。

public class Callback
{
    public Control Control { get; set; }
    public Action Method { get; set; }
}

次に、別のスレッドで (BeginInvoke を使用して) ランダム コードを呼び出し、コードの実行が終了するとメッセージ ボックスを表示する WinForms プロジェクトがあります。

    private void Form1_Load(object sender, EventArgs e)
    {
        Action<bool> act = (bool myBool) =>
            {
                Thread.Sleep(5000);
            };

        act.BeginInvoke(true, new AsyncCallback((IAsyncResult result) =>
        {
            Callback c = result.AsyncState as Callback;
            c.Control.Invoke(c.Method);

        }), new Callback()
        {
            Control = this,
            Method = () => { ShowMessageBox(); }
        });            
    }

ShowMessageBox メソッドは UI スレッドで実行する必要があり、次のようになります。

    private void ShowMessageBox()
    {
        MessageBox.Show("Testing");
    }

これはあなたが探していたものですか?

于 2010-01-07T17:27:05.533 に答える
0

コンポーネントを常に同じスレッドで使用する必要がある場合は、次のようにすることができます。

public delegate void CallbackInvoker(Delegate method, params object[] args);

public YourComponent(CallbackInvoker invoker)
{
    m_invoker = invoker;
}

protected void RaiseEvent(Delegate eventDelegate, object[] args)
{
    if (eventDelegate != null)
    {
        try
        {
            if (m_invoker != null)
                m_invoker(eventDelegate, args);
            else
                eventDelegate.DynamicInvoke(args);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.GetType());
            Console.WriteLine(ex.Message);
            //Swallow
        }
    }
}

次に、フォームまたは他のコントロールからコンポーネントをインスタンス化するときに、次のことができます。

YourComponent c = new YourComponent(this.Invoke);

非 UI ワーカー スレッドでイベントをキューに入れるには、なんらかのワーク キューイング メカニズムが必要です。次に、CallbackInvoker の署名を含むメソッドを指定して、デリゲートをワーカー スレッドでキューに入れることができます。

于 2010-01-07T17:34:15.767 に答える