2011-09-20 9 views
9

Prenez les classes suivantes à titre d'exemple.C# Appel asynchrone sans EndInvoke?

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

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

Maintenant, je veux la méthode appeler test.Foo (myStruct) être un appel asynchrone ('fire-and-forget'). La méthode de barre doit retourner dès que possible. La documentation autour des délégués, BeginInvoke, EndInvoke, ThreadPool etc. ne m'aide pas à trouver une solution.

Est-ce une solution valide?

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

Répondre

11

Vous n'êtes pas obligé d'appeler EndInvoke; ne pas l'appeler signifie simplement:

  • Vous n'obtenez pas la valeur de retour de la méthode.
  • Toutes les exceptions levées pendant l'exécution de la méthode disparaîtront simplement.

On dirait que vous voulez « feu et oublier », de sorte que le moyen le plus simple est d'utiliser un délégué anonyme, par exemple:

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

C'est ce qui se passe lorsque vous exécutez ce code:

  1. Un nouveau thread est alloué (simplement) pour le délégué. Le thread reçoit le délégué del et le délégué anonyme (iar => ...).
  2. Le thread exécute del.
  3. Lorsque l'exécution est terminée (ou qu'une exception se produit), le résultat ou l'exception est stocké et le délégué anonyme est exécuté.
  4. À l'intérieur du délégué anonyme, lorsque le EndInvoke est appelé, le résultat de la méthode est renvoyé ou l'exception est levée (le cas échéant).

Notez que l'exemple ci-dessus est très différent de:

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

Edit: Vous devriez toujours appeler End*. Je ne l'ai jamais trouvé un scénario où ne pas l'appeler pose un problème, mais c'est un détail de mise en œuvre et est relying on undocumented behavior.

Enfin, votre solution échouerait le processus si une exception est levée, vous pouvez tout simplement passer null comme délégué si vous ne vous souciez pas de l'exception ( del.BeginInvoke(myStruct, null, null);). Donc, comme un dernier exemple ce que vous cherchez est probablement:

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

Il est vrai que vous n'êtes pas « nécessaire » pour appeler 'EndInvoke', mais si vous ne Tu vas avoir des fuites de mémoire. http://stackoverflow.com/questions/1712741/why-does-asynchronous-delegate-method-require-calling-endinvoke?rq=1 –

+0

@MattKlein non non. https://gist.github.com/jcdickinson/9109599. La réponse de SLaks est quelque peu correcte cependant, dans certains scénarios, un suivi est effectué avec des paires Begin/End-Invoke. Un exemple est: si vous n'appelez pas EndInvoke sur les opérations Socket, vos compteurs de performance de socket vont complètement détraqué (pas de fuite de mémoire, les valeurs seront juste follement incorrectes). –

+0

Peut-être que ce serait un commentaire utile à ajouter à la réponse de SLaks. –

2

Je dirais que votre meilleure option est d'utiliser le ThreadPool:

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

Cela file d'attente de l'extrait pour l'exécution dans un thread séparé. Maintenant, vous devez également faire attention à autre chose: si vous avez plusieurs threads accédant à la même instance de A et que cette instance modifie une variable, vous devez vous assurer que vous effectuez une synchronisation correcte 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

Vous pouvez utiliser le modèle de rappel expliqué @What is AsyncCallback?

De cette façon, votre EndInvoke ne sera pas dans le bar(), mais dans une méthode de rappel séparée.

Dans l'exemple, le EndRead (correspondant à EndInvoke est dans la méthode de rappel appelée CompleteRead plutôt que la méthode d'appel TestCallbackAPM correspondant à la barre)

0

C'est une option:

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