7

我正在.Net 2.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
            }
        }
    }

任何帮助,将不胜感激。提前致谢!

编辑:根据这个线程,我最好的选择是使用 SynrhonizationContext.Post 但我看不到如何将它应用于我的情况。

4

4 回答 4

2

行; 所以这就是我在阅读更多内容后得到的结果:

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

也许我不理解这个问题,但在我看来,您可以在异步状态下传递对自定义对象的引用。

我整理了下面的例子来说明;

首先我们有一个回调对象。这有 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");
    }

这是你要找的吗?

于 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
        }
    }
}

然后,当您从 Form 或其他控件实例化您的组件时,您可以这样做:

YourComponent c = new YourComponent(this.Invoke);

要在非 UI 工作线程上对事件进行排队,它必须具有某种工作排队机制,然后您可以提供一个带有 CallbackInvoker 签名的方法,以便在工作线程上对委托进行排队。

于 2010-01-07T17:34:15.767 回答