UI 线程中异步组件的触发事件

发布于 2024-08-17 02:54:30 字数 3054 浏览 5 评论 0原文

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

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

编辑: 根据 这个线程 我最好的选择是使用 SyncrhonizationContext.Post 但我不知道如何将其应用到我的情况。

I'm building a non-visual component in .Net 2.0. This component uses an asynchronous socket (BeginReceive, EndReceive etc). Asynchronous callbacks are called in the context of a worker thread created by the runtime. The component user shouldn't have to worry about multithreading (This is the main goal, what I want)

The component user can create my non-visual component in any thread (the UI thread is just a common thread for simple applications. More serious applications could create the component within an arbitrary worker thread). The component trigger events such as "SessionConnected" or "DataAvailable".

The issue: because of the Async Callbacks and the events raised therein the event handler is executed in the worker thread context. I want to use an intermediate layer which force
the event handler to execute in the context of the thread which created the
component at the first place.

Example code (stripped from exception handling etc...)

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

Because of the nature of the Async sockets all applications using my component are littered with "If (this.InvokeRequired) { ..." and all I want is the user to be able to use my component worry-free as sort of a drop-in.

So how would I go about raising the events without requiring the user to check InvokeRequired (or, put differently, how do I force the events raised in the same thread as the thread that initiated the event in the first place)?

I have read stuff about AsyncOperation, BackgroundWorkers, SynchronizingObjects, AsyncCallbacks and tons of other stuff but it all makes my head spin.

I did come up with this, surely clumsy, "solution" but it seems to fail in some situations (when my component is called from a WinForms project via a static class for example)

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

Any help would be appreciated. Thanks in advance!

EDIT:
According to this thread my best bet would be to use SyncrhonizationContext.Post but I can't see how to apply it to my situation.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

溺ぐ爱和你が 2024-08-24 02:54:30

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

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)是迄今为止最好的。非常非常欢迎提出建议。

Ok; so here's what I ended up with after some more reading:

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

The other solution posted here was sort of a work-in-progress. The solution posted here seems (according to MSDN) be the best so far. Suggestions are very, very welcome.

迷雾森÷林ヴ 2024-08-24 02:54:30

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

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

我仍在测试它,但它似乎工作正常。

I seem to have found my solution:

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

I'm still testing this but it seems to work fine.

最后的乘客 2024-08-24 02:54:30

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

我整理了以下例子来说明;

首先我们有一个回调对象。它有 2 个属性——一个用于分派操作的 Control 和一个用于调用的 Action;

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

这是您正在寻找的吗?

Maybe I'm not understanding the issue, but it seems to me that you can just pass a reference to a custom object in your Async state.

I put together the following example to illustrate;

First we have a Callback object. This has 2 properties -- a Control on which to dispatch actions and an Action to call;

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

Then I have a WinForms project that calls some random code on another thread (using BeginInvoke) and then shows a messagebox when the code finishes executing.

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

The ShowMessageBox method must run on the UI thread and looks like:

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

Is this what you were looking for?

心欲静而疯不止 2024-08-24 02:54:30

如果您的组件必须始终由同一个线程使用,您可以执行以下操作:

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 签名的方法来将委托在工作线程上排队。

If your component must always be used by the same thread, you could do something like this:

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

Then when you instantiate your component from a Form or other control you can do this:

YourComponent c = new YourComponent(this.Invoke);

To queue the event on a non UI worker thread, it must have some sort of work queuing mechanism, then you can give a method with CallbackInvoker's signature to queue the delegate on the worker thread.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文