简体   繁体   English

来自UI线程中的Async组件的Fire事件

[英]Fire event from Async component in UI thread

I'm building a non-visual component in .Net 2.0. 我正在.Net 2.0中构建一个非可视组件。 This component uses an asynchronous socket (BeginReceive, EndReceive etc). 该组件使用异步套接字(BeginReceive,EndReceive等)。 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). 组件用户可以在任何线程中创建我的非可视组件(UI线程只是简单应用程序的通用线程。更严重的应用程序可以在任意工作线程中创建组件)。 The component trigger events such as "SessionConnected" or "DataAvailable". 组件触发事件,例如“SessionConnected”或“DataAvailable”。

The issue: because of the Async Callbacks and the events raised therein the event handler is executed in the worker thread context. 问题:由于Async Callbacks和其中引发的事件,事件处理程序在工作线程上下文中执行。 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. 由于Async套接字的性质,使用我的组件的所有应用程序都充满了“If(this.InvokeRequired){...”,我想要的只是用户能够无忧无虑地使用我的组件作为一种丢弃-在。

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)? 那么我如何在不要求用户检查InvokeRequired的情况下提升事件(或者换句话说,如何强制在与首先发起事件的线程相同的线程中引发的事件)?

I have read stuff about AsyncOperation, BackgroundWorkers, SynchronizingObjects, AsyncCallbacks and tons of other stuff but it all makes my head spin. 我已经阅读过有关AsyncOperation,BackgroundWorkers,SynchronizingObjects,AsyncCallbacks以及大量其他内容的内容,但这些都让我头晕目眩。

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) 我确实提出了这个,肯定是笨拙的“解决方案”,但在某些情况下它似乎失败了(例如当我通过静态类从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
            }
        }
    }

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. 编辑:根据这个线程,我最好的选择是使用SyncrhonizationContext.Post,但我看不到如何将它应用到我的情况。

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. 这里发布的解决方案似乎(根据MSDN)是迄今为止最好的解决方案。 Suggestions are very, very welcome. 建议非常非常欢迎。

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. 我还在测试这个,但似乎工作正常。

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. 也许我不理解这个问题,但在我看来,您可以在Async状态下传递对自定义对象的引用。

I put together the following example to illustrate; 我把以下例子放在一起来说明;

First we have a Callback object. 首先,我们有一个Callback对象。 This has 2 properties -- a Control on which to dispatch actions and an Action to call; 这有2个属性 - 一个用于调度动作的控件和一个要调用的动作;

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. 然后我有一个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(); }
        });            
    }

The ShowMessageBox method must run on the UI thread and looks like: ShowMessageBox方法必须在UI线程上运行,如下所示:

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

Is this what you were looking for? 这是你在找什么?

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. 要在非UI工作线程上对事件进行排队,它必须具有某种工作排队机制,然后您可以为具有CallbackInvoker签名的方法提供在工作线程上对委托进行排队的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM