Событие пожара из компонента 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();
}
}
Из-за природы асинхронных сокетов все приложения, использующие мой компонент, замусорены "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, но я не вижу, как применить его к моей ситуации.
4 ответа
Хорошо; так вот что я закончил после некоторого дополнительного чтения:
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) лучшим на сегодняшний день. Предложения очень, очень приветствуются.
Кажется, я нашел свое решение:
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);
}
}
Я все еще проверяю это, но, кажется, работает нормально.
Если ваш компонент всегда должен использоваться одним и тем же потоком, вы можете сделать что-то вроде этого:
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 для постановки в очередь делегата в рабочем потоке.
Может быть, я не понимаю проблему, но мне кажется, что вы можете просто передать ссылку на пользовательский объект в состоянии 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");
}
Это то, что ты искал?