2010-01-07 20 views
7

Estoy creando un componente no visual en .Net 2.0. Este componente utiliza un socket asíncrono (BeginReceive, EndReceive, etc.). Las devoluciones de llamada asincrónicas se llaman en el contexto de un subproceso de trabajo creado por el tiempo de ejecución. El usuario componente no debería tener que preocuparse por el multihilo (este es el objetivo principal, lo que yo quiero)Evento de incendio del componente Async en el subproceso UI

El usuario componente puede crear mi componente no visual en cualquier hilo (el hilo de la interfaz de usuario es un hilo común para simple aplicaciones. Las aplicaciones más serias podrían crear el componente dentro de un hilo de trabajo arbitrario). El componente desencadena eventos como "SessionConnected" o "DataAvailable".

El problema: debido a las rellamadas asíncronas y los eventos que se generan, el controlador de eventos se ejecuta en el contexto del hilo de trabajo. Quiero utilizar una capa intermedia que fuerza el controlador de eventos para ejecutar en el contexto de la secuencia que creó el componente en primer lugar.

Código de ejemplo (despojado de gestión de excepciones, 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(); 
     } 
    } 

Debido a la naturaleza de la asíncrono sockets todas las aplicaciones que usan mi componente están llenas de "Si (this.InvokeRequired) {... "y todo lo que quiero es que el usuario pueda usar mi componente sin preocupaciones como una especie de complemento.

Entonces, ¿cómo haré para plantear los eventos sin requerir que el usuario revise InvokeRequired (o, dicho de otro modo, cómo fuerzo los eventos planteados en el mismo hilo que el hilo que inició el evento en primer lugar)?

He leído cosas sobre AsyncOperation, BackgroundWorkers, SynchronizingObjects, AsyncCallbacks y toneladas de otras cosas, pero todo hace girar la cabeza.

Yo he venido para arriba con esto, sin duda torpe, "solución" pero parece fallar en algunas situaciones (cuando mi componente se llama desde un proyecto de Windows Forms a través de una clase estática, por ejemplo)

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

Cualquier ayuda sería apreciada ¡Gracias por adelantado!

EDIT: De acuerdo con this thread mi mejor opción sería usar SyncrhonizationContext.Post pero no veo cómo aplicarlo a mi situación.

Respuesta

2

Ok; Así que aquí está lo que terminó con después de leer un poco más:

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

La otra solución publicado aquí era una especie de trabajo en progreso. La solución publicada aquí parece (según MSDN) ser la mejor hasta el momento. Las sugerencias son muy, muy bienvenidas.

0

Tal vez no entiendo el problema, pero me parece que puede pasar una referencia a un objeto personalizado en su estado Async.

He reunido el siguiente ejemplo para ilustrar;

Primero tenemos un objeto de devolución de llamada. Esto tiene 2 propiedades: un control sobre el cual enviar acciones y una acción para llamar;

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

entonces tengo un proyecto WinForms que llama a un código aleatorio en otro hilo (usando BeginInvoke) y luego muestra un cuadro de mensaje cuando el código termina de ejecutarse.

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

El método ShowMessageBox debe ejecutarse en el subproceso de interfaz de usuario y se ve como:

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

Es esto lo que estabas buscando?

+0

No realmente; Esto es complejo para las personas al usar mi componente. Todo lo que hace es hacer referencia al componente y usar código como: // Inicializar MyComponent con IP, puerto, etc. y luego: MyComponent.SendString ("This is cool"); MyComponent plantea eventos; ya sea que se manejen mediante un proyecto GUI o no GUI no debería importar y no quiero que el usuario sea responsable de verificar InvokeRequired. – ComponentBuilder

0

Si el componente se debe utilizar siempre el mismo hilo, se puede hacer algo como esto:

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

Entonces, cuando usted instancia su componente de una u otra forma de control puede hacer esto:

YourComponent c = new YourComponent(this.Invoke); 

Para poner en cola el evento en un hilo de trabajo no UI, debe tener algún tipo de mecanismo de cola de trabajo, luego puede dar un método con la firma de CallbackInvoker para poner en cola al delegado en el hilo de trabajo.

+0

Esto pone demasiada complejidad en el lapso del usuario que usa mi componente. – ComponentBuilder

+0

¿Dónde está esa complejidad? ¿Especificando un delegado en el constructor del componente? –

+0

Quiero que mi componente sea asincrónicamente transparente (para que el usuario final no lo note). Cuando el usuario final tiene que pasar a un delegado sin motivo (obvio), creo que no es "fácil de usar". Parece que encontré mi solución; pero no estoy seguro de si es la "mejor" solución. – ComponentBuilder

1

parece que he encontrado mi solución:

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

Todavía estoy probando esto, pero parece que funciona bien.

+0

¿Qué sucederá con este enfoque cuando su componente no se crea en el hilo de la interfaz de usuario? –

+0

Olvidé pegar mi última edición, que comprueba si _currentcontext es nulo; esto ahora está arreglado (y editado). – ComponentBuilder

+0

Acabo de comprobar, si captura el SynchronizationContext en el constructor del componente y el componente se crea en un subproceso que no sea de UI, su eventDelegate se ejecutará en el ThreadPool. –

Cuestiones relacionadas