簡體   English   中英

來自UI線程中的Async組件的Fire事件

[英]Fire event from Async component in UI thread

我正在.Net 2.0中構建一個非可視組件。 該組件使用異步套接字(BeginReceive,EndReceive等)。 異步回調在運行時創建的工作線程的上下文中調用。 組件用戶不必擔心多線程(這是主要目標,我想要的)

組件用戶可以在任何線程中創建我的非可視組件(UI線程只是簡單應用程序的通用線程。更嚴重的應用程序可以在任意工作線程中創建組件)。 組件觸發事件,例如“SessionConnected”或“DataAvailable”。

問題:由於Async Callbacks和其中引發的事件,事件處理程序在工作線程上下文中執行。 我想使用一個中間層,它強制事件處理程序在首先創建組件的線程的上下文中執行。

示例代碼(從異常處理等中刪除...)

    /// <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();
        }
    }

由於Async套接字的性質,使用我的組件的所有應用程序都充滿了“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,但我看不到如何將它應用到我的情況。

好; 所以這是我在閱讀之后得到的結論:

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)是迄今為止最好的解決方案。 建議非常非常歡迎。

我似乎找到了我的解決方案:

    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);
        }
    }

我還在測試這個,但似乎工作正常。

也許我不理解這個問題,但在我看來,您可以在Async狀態下傳遞對自定義對象的引用。

我把以下例子放在一起來說明;

首先,我們有一個Callback對象。 這有2個屬性 - 一個用於調度動作的控件和一個要調用的動作;

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

然后我有一個WinForms項目,在另一個線程上調用一些隨機代碼(使用BeginInvoke),然后在代碼完成執行時顯示一個消息框。

    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");
    }

這是你在找什么?

如果您的組件必須始終由同一個線程使用,您可以執行以下操作:

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簽名的方法提供在工作線程上對委托進行排隊的方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM