2011-09-20 26 views
9

Prendere le seguenti classi come esempio.C# Chiamata asincrona senza EndInvoke?

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

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

Ora, io voglio il metodo-chiamata test.Foo (myStruct) di essere una chiamata asincrona ('fire-and-forget'). Il metodo della barra deve essere restituito il prima possibile. La documentazione sui delegati, BeginInvoke, EndInvoke, ThreadPool, ecc. Non mi sta aiutando a trovare una soluzione.

È una soluzione valida?

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

risposta

11

Non è necessario chiamare EndInvoke; non chiamarlo significa semplicemente:

  • Non si ottiene il valore restituito dal metodo.
  • Eventuali eccezioni generate durante l'esecuzione del metodo spariranno semplicemente.

Sembra che si desidera 'fire-and-forget', quindi il modo più semplice per farlo è quello di utilizzare un delegato anonimo, ad esempio:

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

Questo è ciò che accade quando si eseguire questo codice:

  1. Un nuovo thread è allocato (in modo semplice) per il delegato.
  2. Alla discussione viene assegnato il delegato del e il delegato anonimo (iar => ...).
  3. Il thread viene eseguito del.
  4. Al termine dell'esecuzione (o si verifica un'eccezione) il risultato o l'eccezione vengono archiviati e il delegato anonimo viene eseguito.
  5. All'interno del delegato anonimo, quando viene chiamato il numero EndInvoke, viene restituito il risultato del metodo oppure viene generata l'eccezione (se verificata).

Si noti che l'esempio di cui sopra è molto diverso da:

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

Edit: Si dovrebbe sempre chiamare End*. Non ho mai trovato uno scenario in cui non definendolo presenta un problema, tuttavia, che è un dettaglio di implementazione ed è relying on undocumented behavior.

Infine la soluzione potrebbe andare in crash il processo se viene generata un'eccezione, si può semplicemente passare null come delegato se non ti interessa l'eccezione ( del.BeginInvoke(myStruct, null, null);). Così come un ultimo esempio quello che stai cercando è probabilmente:

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

È vero, non si è "necessaria" per chiamare 'EndInvoke', ma se don riceverai perdite di memoria. http://stackoverflow.com/questions/1712741/why-does-asynchronous-delegate-method-require-calling-endinvoke?rq=1 –

+0

@MattKlein no no. https://gist.github.com/jcdickinson/9109599. La risposta di SLaks è alquanto corretta, tuttavia, in alcuni scenari alcune tracce vengono eseguite con coppie 'Begin/End-Invoke' - un esempio è: se non si chiama' EndInvoke' su 'Socket', i contatori delle prestazioni del socket andranno completamente out of whack (nessuna perdita di memoria, i valori saranno semplicemente errati). –

+0

Forse questo sarebbe un commento prezioso da aggiungere alla risposta di SLaks. –

2

Direi che la soluzione migliore è quella di utilizzare il ThreadPool:

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

Ciò coda il frammento per l'esecuzione in un thread separato. Ora devi anche fare attenzione a qualcos'altro: se hai più thread che accedono alla stessa istanza di A e quell'istanza modifica una variabile, devi assicurarti di fare la corretta sincronizzazione della variabile.

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

È possibile utilizzare il modello di richiamata spiegato @What is AsyncCallback?

In questo modo l'EndInvoke non sarà in bar(), ma in un metodo di callback separata.

Nell'esempio, l'EndRead (corrispondente a EndInvoke è nel metodo di callback chiamata CompleteRead piuttosto che il metodo chiamante TestCallbackAPM corrispondente al bar)

0

Questa è un'opzione:

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