Событие пожара из компонента Async в потоке пользовательского интерфейса - PullRequest
7 голосов
/ 07 января 2010

Я создаю невизуальный компонент в .Net 2.0. Этот компонент использует асинхронный сокет (BeginReceive, EndReceive и т. Д.). Асинхронные обратные вызовы вызываются в контексте рабочего потока, созданного средой выполнения. Пользователю компонента не нужно беспокоиться о многопоточности (это главная цель, чего я хочу)

Пользователь компонента может создать мой невизуальный компонент в любом потоке (поток пользовательского интерфейса - это просто общий поток для простых приложений. Более серьезные приложения могут создавать компонент в произвольном рабочем потоке). События запуска компонента, такие как «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
            }
        }
    }

Любая помощь будет оценена. Заранее спасибо!

EDIT: Согласно этой теме лучше всего было бы использовать SyncrhonizationContext.Post, но я не понимаю, как применить его к моей ситуации.

Ответы [ 4 ]

2 голосов
/ 08 января 2010

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) лучшим на сегодняшний день. Предложения очень, очень приветствуются.

1 голос
/ 07 января 2010

Кажется, я нашел свое решение:

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

Я все еще тестирую это, но, похоже, работает нормально.

0 голосов
/ 07 января 2010

Если ваш компонент всегда должен использоваться одним и тем же потоком, вы можете сделать что-то вроде этого:

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

Чтобы поставить в очередь событие в рабочем потоке, не являющемся пользовательским интерфейсом, у него должен быть какой-то механизм работы с очередями, после чего вы можете задать метод с подписью CallbackInvoker для помещения в очередь делегата в рабочем потоке.

0 голосов
/ 07 января 2010

Возможно, я не понимаю проблему, но мне кажется, что вы можете просто передать ссылку на пользовательский объект в состоянии 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 должен выполняться в потоке пользовательского интерфейса и выглядит следующим образом:

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

Это то, что вы искали?

...