2011-09-20 25 views
9

Tome las siguientes clases como ejemplo.C# Llamada asincrónica sin EndInvoke?

public class A 
{ 
    // ... 
    void Foo(S myStruct){...} 
} 

public class B 
{ 
    public A test; 
    // ... 
    void Bar() 
    { 
     S myStruct = new S(); 
     test.Foo(myStruct); 
    } 
} 

Ahora, yo quiero el método de llamar test.Foo (myStruct) para ser una llamada asincrónica ('dispara y olvida'). El método de barra debe regresar lo más pronto posible. La documentación sobre los delegados, BeginInvoke, EndInvoke, ThreadPool, etc. no me ayuda a encontrar una solución.

¿Es esta una solución válida?

 // Is using the `EndInvoke` method as the callback delegate valid? 
    foo.BeginInvoke(myStruct, foo.EndInvoke, null); 

Respuesta

11

Usted no está obligado a llamar EndInvoke; no llamarlo meramente significa:

  • No obtiene el valor de retorno del método.
  • Cualquier excepción lanzada durante la ejecución del método simplemente desaparecerá.

Parece que usted quiere 'dispara y olvida', por lo que la forma más fácil de hacer esto es utilizar un delegado en el anonimato, por ejemplo:

var del = new Action(foo.Bar); 
del.BeginInvoke(iar => 
{ 
    try 
    { 
     del.EndInvoke(iar); 
    } 
    catch (Exception ex) 
    { 
     // Log the message? 
    } 
}, null); 

Esto es lo que sucede cuando se ejecute este código:

  1. Un nuevo hilo se asigna (en pocas palabras) para el delegado.
  2. El hilo se le da al delegado del y el delegado anónimo (iar => ...).
  3. El hilo ejecuta del.
  4. Cuando termina de ejecutarse (o se produce una excepción), el resultado o la excepción se almacena y se ejecuta el delegado anónimo.
  5. Dentro del delegado anónimo, cuando se llama EndInvoke, se devuelve el resultado del método o se produce la excepción (si se produjo).

Tenga en cuenta que el ejemplo anterior es muy diferente de:

// This is pointless and is still, essentially, synchronous. 
del.EndInvoke(del.BeginInvoke(null, null)); 

Editar: siempre debe llamar End*. Nunca he encontrado un escenario donde no llamarlo presenta un problema, sin embargo, eso es un detalle de implementación y es relying on undocumented behavior.

Finalmente su solución bloquearía el proceso si se lanza una excepción, simplemente puede pasar nulo como delegado si no le importa la excepción ( del.BeginInvoke(myStruct, null, null);). Así como un ejemplo final lo que busca es probablemente:

public class A 
{ 
    // ... 
    void Foo(S myStruct){...} 
    void FooAsync(S myStruct) 
    { 
     var del = new Action<S>(Foo); 
     del.BeginInvoke(myStruct, SuppressException, del); 
    } 

    static void SuppressException(IAsyncResult ar) 
    { 
     try 
     { 
      ((Action<S>)ar.AsyncState).EndInvoke(ar); 
     } 
     catch 
     { 
      // TODO: Log 
     } 
    } 
} 
+0

Es cierto que usted no es "necesaria" para llamar 'EndInvoke', pero si don Tendrás filtraciones de memoria. http://stackoverflow.com/questions/1712741/why-does-asynchronous-delegate-method-require-calling-endinvoke?rq=1 –

+0

@MattKlein no, no es así. https://gist.github.com/jcdickinson/9109599. La respuesta de SLaks es algo correcta, sin embargo, en ciertos escenarios se realiza un seguimiento con pares 'Begin/End-Invoke' - un ejemplo es: si no llamas' EndInvoke' en las operaciones 'Socket' tus contadores de rendimiento de socket se apagarán por completo fuera de sintonía (sin pérdida de memoria, los valores serán simplemente muy incorrectos). –

+0

Quizás este sea un comentario valioso para agregar a la respuesta de SLaks. –

2

diría que su mejor opción es utilizar el ThreadPool:

void bar() 
{ 
    ThreadPool.QueueUserWorkItem(o=> 
    { 
     S myStruct = new S(); 
     test.foo(myStruct); 
    }); 
} 

Esto Cola del fragmento para la ejecución en un hilo separado. Ahora también debe tener cuidado con otra cosa: si tiene varios subprocesos que acceden a la misma instancia de A y esa instancia modifica una variable, debe asegurarse de realizar la sincronización adecuada de la variable.

public class A 
{ 
    private double sum; 
    private volatile bool running; 
    private readonly object sync; 
    public A() 
    { 
     sum = 0.0; 
     running = true; 
     sync = new object(); 
    } 

    public void foo(S myStruct) 
    { 
     // You need to synchronize the whole block because you can get a race 
     // condition (i.e. running can be set to false after you've checked 
     // the flag and then you would be adding the sum when you're not 
     // supposed to be). 
     lock(sync) 
     { 
      if(running) 
      { 
       sum+=myStruct.Value; 
      } 
     } 
    } 

    public void stop() 
    { 
     // you don't need to synchronize here since the flag is volatile 
     running = false; 
    } 
} 
1

Usted puede utilizar el modelo de devolución de llamada explicó @What is AsyncCallback?

De esa manera su EndInvoke no estará en bar(), pero en un método de devolución de llamada por separado.

En el ejemplo, el EndRead (correspondiente a EndInvoke es en el método de devolución de llamada llama CompleteRead en lugar de la TestCallbackAPM método de llamada que corresponde a la barra)

0

Esta es una opción:

ThreadPool.QueueUserWorkItem(bcl => 
{ 
    var bcList = (List<BarcodeColumn>)bcl; 
    IAsyncResult iftAR = this.dataGridView1.BeginInvoke((MethodInvoker)delegate 
    { 
     int x = this.dataGridView1.Rows[0].Cells.Count - 1; 
     for (int i = 0; i < this.dataGridView1.Rows.Count - 1; i++) 
     { 
      try 
      { 
       string imgPath = bcList[i].GifPath; 
       Image bmpImage = Image.FromFile(imgPath); 
       this.dataGridView1.Rows[i].Cells[x].Value =bmpImage; 
      } 
      catch (Exception) 
      { 
       continue; 
      } 
     } 
    }); 
    while (!iftAR.IsCompleted) { /* wait this*/ } 
}, barcodeList); 
Cuestiones relacionadas