От новичка до гуру: Курсы программирования на CyberDuff

Событие Fire из компонента Async в потоке пользовательского интерфейса

Я создаю невизуальный компонент в .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();
        }
    }

Из-за природы сокетов 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 но я не понимаю, как применить это к моей ситуации.


Ответы:


1

В порядке; Итак, вот что у меня получилось после еще некоторого чтения:

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

08.01.2010

2

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

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

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

07.01.2010
  • Что произойдет с этим подходом, если ваш компонент не будет создан в потоке пользовательского интерфейса? 07.01.2010
  • Я забыл вставить свою последнюю правку, которая проверяет, является ли _currentcontext нулевым; теперь это исправлено (и отредактировано). 07.01.2010
  • Я только что проверил, если вы захватываете SynchronizationContext в конструкторе компонента и компонент создается в потоке без пользовательского интерфейса, ваш eventDelegate будет выполнен в ThreadPool. 07.01.2010
  • Сейчас я использую WindowsFormsSynchronizationContext (я снова отредактировал свое решение). Будет ли это иметь значение? Боюсь, я совершенно не разбираюсь в этой теме. Я проверил это с помощью консольного приложения и приложения WinForms, и оба, похоже, работают нормально. 07.01.2010

  • 3

    Возможно, я не понимаю проблемы, но мне кажется, что вы можете просто передать ссылку на настраиваемый объект в своем состоянии Async.

    Я собрал следующий пример, чтобы проиллюстрировать;

    Сначала у нас есть объект обратного вызова. У него есть 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");
        }
    

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

    07.01.2010
  • Не совсем; это усложняет людям использование моего компонента. Все, что нужно сделать, - это сослаться на компонент и использовать такой код: // Инициализировать MyComponent с IP, портом и т. Д., А затем: MyComponent.SendString (это круто); MyComponent вызывает события; обрабатываются ли они проектом с графическим интерфейсом или без него, не имеет значения, и я не хочу, чтобы пользователь отвечал за проверку наличия InvokeRequired. 07.01.2010

  • 4

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

    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 для постановки делегата в очередь в рабочем потоке.

    07.01.2010
  • Это создает слишком большую сложность для пользователя, использующего мой компонент. 07.01.2010
  • Где эта сложность? Указываете делегата в конструкторе компонента? 07.01.2010
  • Я хочу, чтобы мой компонент был прозрачно асинхронным (чтобы конечный пользователь этого не заметил). Когда конечный пользователь должен передать делегата без (очевидной) причины, я думаю, что это не удобно для пользователя. Но я, кажется, нашел свое решение; но я не уверен, что это лучшее решение. 07.01.2010
  • Новые материалы

    5 простых концепций Python, ставших сложными
    #заранее извините 1) Переменные x = 4 y = 5 Переменная в Python — это символическое представление объекта. После присвоения некоторого объекта переменной Python мы приобретаем..

    «Освоение вероятности: изучение совместной, предельной, условной вероятности и теоремы Байеса —…
    Виды вероятности: Совместная вероятность Предельная вероятность Условная вероятность Диаграмма Венна в вероятностях: В “Set Theory” мы создаем диаграмму Венна...

    Основы Spring: Bean-компоненты, контейнер и внедрение зависимостей
    Как лего может помочь нашему пониманию Когда мы начинаем использовать Spring, нам бросают много терминов, и может быть трудно понять, что они все означают. Итак, мы разберем основы и будем..

    Отслеживание состояния с течением времени с дифференцированием снимков
    Время от времени что-то происходит и революционизирует часть моего рабочего процесса разработки. Что-то более забавное вместо типичного утомительного и утомительного процесса разработки. В..

    Я предполагаю, что вы имеете в виду методы обработки категориальных данных.
    Я предполагаю, что вы имеете в виду методы обработки категориальных данных. Пожалуйста, проверьте мой пост Инструментарий специалиста по данным для кодирования категориальных переменных в..

    Игра в прятки с данными
    Игра в прятки с данными Я хотел бы, чтобы вы сделали мне одолжение и ответили на следующие вопросы. Гуглить можно в любое время, здесь никто не забивается. Сколько регионов в Гане? А как..

    «Раскрытие математических рассуждений с помощью Microsoft MathPrompter и моделей больших языков»
    TL;DR: MathPrompter от Microsoft показывает, как использовать математические рассуждения с большими языковыми моделями; 4-этапный процесс для улучшения доверия и рассуждений в математических..