2009-10-09 3 views
18

je le code suivant:BeginExecuteNonQuery sans EndExecuteNonQuery

using (SqlConnection sqlConnection = new SqlConnection("blahblah;Asynchronous Processing=true;") 
{ 
    using (SqlCommand command = new SqlCommand("someProcedureName", sqlConnection)) 
    { 
     sqlConnection.Open(); 

     command.CommandType = CommandType.StoredProcedure; 
     command.Parameters.AddWithValue("@param1", param1); 

     command.BeginExecuteNonQuery(); 
    } 
} 

j'appelle jamais EndExecuteNonQuery. Deux questions, d'abord ce bloc en raison des instructions using ou pour toute autre raison? Deuxièmement, cela va-t-il casser quoi que ce soit? Comme des fuites ou des problèmes de connexion? Je veux juste dire au serveur sql d'exécuter une procédure stockée, mais je ne veux pas l'attendre et je m'en fous même si ça marche. Est-ce possible? Merci d'avoir lu.

+0

hmm, je suppose que je vais aller avec le ThreadPool.QueueUserWorkItem, mais il semble juste une perte que je dois utiliser un thread dans mon application pour surveiller quelque chose que je ne me soucie pas. Je veux juste donner une commande à sqlServer et l'oublier. Il semble que j'ai une incompréhension fondamentale sur le fonctionnement des connexions au db car je ne comprends vraiment pas pourquoi il n'est pas possible de le faire. Quelqu'un at-il un lien qui explique pourquoi cela n'est pas possible? –

+2

BeginExecuteNonQuery utilise quand même le ThreadPool, donc vous ne faites rien ce qu'il ne ferait pas autrement. La raison pour laquelle vous ne pouvez pas compter sur ceci est que BeginExecuteNonQuery ne bloque pas jusqu'à ce que la requête se termine, vous pouvez fermer la connexion pendant que la requête est toujours en cours en arrière-plan. – SLaks

Répondre

24

Cela ne fonctionnera pas car vous fermez la connexion pendant que la requête est en cours d'exécution. La meilleure façon de le faire serait d'utiliser le threadpool, comme ceci:

ThreadPool.QueueUserWorkItem(delegate { 
    using (SqlConnection sqlConnection = new SqlConnection("blahblah;Asynchronous Processing=true;") { 
     using (SqlCommand command = new SqlCommand("someProcedureName", sqlConnection)) { 
      sqlConnection.Open(); 

      command.CommandType = CommandType.StoredProcedure; 
      command.Parameters.AddWithValue("@param1", param1); 

      command.ExecuteNonQuery(); 
     } 
    } 
}); 

En général, lorsque vous appelez Begin_Whatever_, vous devez généralement appeler End_Whatever_ ou vous fuite de mémoire. La grande exception à cette règle est Control.BeginInvoke.

+0

Mais la procédure est en effet terminée. Je peux voir les enregistrements qu'il insère plus tard. ??? –

+1

Mais vous ne pouvez pas compter dessus toujours en terminant avant de fermer la connexion. Vous pouvez obtenir des bogues subtils et difficiles à reproduire en production lorsque la charge du serveur est élevée. – SLaks

+0

@SLaks - votre solution fonctionne plutôt bien, mais connaissez-vous des performances (dés) avantages à utiliser BeginExecuteNonQuery? Je suppose que je peux voir que cette méthode nécessiterait la création de plusieurs threads de fond, mais je ne dirais pas vraiment que c'est un coup de performance. – n00b

5

Vous devez toujours appeler la méthode EndExecuteNonQuery() pour éviter les fuites. Cela peut fonctionner maintenant mais qui sait ce qui se passera dans les futures versions de .NET. La règle générale est de suivre toujours BeginExecute ... avec un endExecute ...

+3

+1 Début __() tout doit toujours * avoir une fin ___() correspondante. Mais pour la plupart d'entre eux, j'aime mieux l'approche du délégué de threadpool. –

11
  1. Vous ne pouvez pas fermer la connexion après avoir soumis le BeginExceuteNotQuery. Il va annuler l'exécution. Retirez le bloc utilisé.

  2. Afin de fermer la connexion, vous devez savoir quand l'appel est terminé. Pour que vous doit appel EndExecuteNonQuery, généralement à partir d'un rappel:

.

command.BeginExecuteNonQuery(delegate (IAsyncResult ar) { 
    try { command.EndExecuteNonQuery(ar); } 
    catch(Exception e) { /* log exception e */ } 
    finally { sqlConnection.Dispose(); } 
    }, null); 

Si vous souhaitez soumettre une requête et ne se soucient pas des résultats, see Asynchronous T-SQL execution pour un modèle fiable qui assure l'exécution même si diconnects client ou accidents.

+0

Le code de cette réponse fonctionne correctement, mais méfiez-vous si vous transmettez le SqlCommand comme AsyncState. Il a causé une fuite de mémoire difficile à corriger, essentiellement une référence GC circulaire. Encore une fois, rien dans cette réponse est faux, espérant juste sauver les gens un certain temps de diagnostic. –

+0

@Zachary: Cela ne devrait pas causer de problèmes; le GC peut gérer cela très bien. Et vous le faites de toute façon avec la fermeture. – SLaks

+0

@SLaks: C'est ce que je pensais quand je l'ai écrit pour la première fois, le IAsyncResult qui a été transmis au délégué s'est arrêté à la référence à SqlCommand. "Ne devrait pas" est le mot clé ici. –

3

Je sais que c'est un ancien article; tout en ajoutant mon 2c basé sur nos récentes (très concluants) la mise en œuvre et d'essai: D

Pour répondre aux questions de l'OP:

  1. Si vous n'appelez pas EndExecuteNonQuery, BeginExecuteNonQuery exécutera la procédure, mais l'opération sera annulée dès que la clause using disposera de votre connexion sql. Par conséquent, ce n'est pas plausible.
  2. Si vous appelez BeginExecuteNonQuery en utilisant un délégué, en créant un nouveau thread etc. et que vous n'appelez pas EndExecuteNonQuery, il y a de fortes chances que vous receviez des fuites de mémoire en fonction de ce qui se passe dans votre procédure stockée. (Plus à ce sujet plus tard).
  3. L'appel d'une procédure stockée et ne pas attendre la fin de l'appel, dans la mesure où nos tests se sont déroulés, n'est pas possible. Indépendamment du multitâche, quelque chose devra attendre quelque part.

à notre solution:

Réfs: BeginExecuteNonQuery -> BENQ, EndExecuteNonQuery -> EENQ

Cas d'utilisation:

Nous avons un service Windows (C#) qui utilise la bibliothèque .Net TPL. Nous avions besoin de charger des données avec une procédure stockée d'une base de données à l'autre au moment de l'exécution, en fonction d'une requête add-hoc que le service prend en charge. Notre procédure stockée comportait une transaction interne et une gestion des exceptions avec des blocs try catch.

premier essai:

Pour notre premier essai nous a mis en œuvre une solution trouvée ici MS Solution dans cet exemple, vous verrez que MS choisit d'appeler BENQ met alors en oeuvre une boucle while pour bloquer l'exécution et appelle ensuite EENQ. Cette solution a été implémentée principalement si vous n'avez pas besoin d'une méthode de rappel. Le problème avec cette solution est que seul BENQ est ignorant des délais d'attente de connexion SQL. L'EENQ expirera. Donc, pour une requête de longue durée (ce qui est la raison pour laquelle vous utilisez BENQ), vous serez coincé dans le temps et une fois l'opération terminée et vous appelez EENQ, vous obtiendrez une connexion de délai d'attente sql.

Deuxième essai:

Pour notre deuxième essai nous avons pensé ok permet donc appeler BENQ, puis ajoutez un certain temps pour que nous ne fermons pas notre connexion SQL et ne jamais appeler EENQ. Cela a fonctionné, jusqu'à ce qu'une exception ait été levée dans notre procédure stockée. Parce que nous n'avons jamais appelé EENQ, l'opération n'a jamais été terminée et l'exception n'a jamais grimpé jusqu'à notre code. Par conséquent, nous étions coincés dans une fuite de boucle/thread/mémoire pour toujours.

Troisième essai: (La solution)

Pour notre troisième essai nous avons pensé appeler BENQ, puis directement après EENQ d'appel. Ce qui s'est passé, c'est qu'EENQ a effectivement bloqué l'exécution dans le thread jusqu'à la fin de l'opération. Lorsqu'une exception s'est produite dans la procédure stockée, elle a été interceptée. Lorsque la requête était longue, EENQ ne renvoyait pas d'exception de dépassement de délai et, dans tous les cas, notre objet de connexion sql était éliminé ainsi que notre thread.

Voici quelques extraits de notre code:

nous ouvrons ici un nouveau fil pour la méthode qui appelle la procédure stockée.

//Call the load data stored procedure. As this stored procedure can run longer we start it in its own thread. 
Task.Factory.StartNew(() => ClassName.MethodName(Parameters)); 

Ceci est le code dans la méthode que nous utilisons pour appeler la procédure stockée.

//Because this is a long running stored procedure, we start is up in a new thread. 
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings[ConfigurationManager.AppSettings["ConnectionStringName"]].ConnectionString)) 
{ 
    try 
    { 
     //Create a new instance SqlCommand. 
     SqlCommand command = new SqlCommand(ConfigurationManager.AppSettings["StoredProcedureName"], conn); 

     //Set the command type as stored procedure. 
     command.CommandType = CommandType.StoredProcedure; 

     //Create input parameters. 
     command.Parameters.Add(CreateInputParam("@Param1", SqlDbType.BigInt, Param1)); 
     command.Parameters.Add(CreateInputParam("@Param2", SqlDbType.BigInt, Param3)); 
     command.Parameters.Add(CreateInputParam("@Param3", SqlDbType.BigInt, Param3)); 

     //Open up the sql connection. 
     conn.Open(); 

     //Create a new instance of type IAsyncResult and call the sp asynchronously. 
     IAsyncResult result = command.BeginExecuteNonQuery(); 

     //When the process has completed, we end the execution of the sp. 
     command.EndExecuteNonQuery(result); 
    } 
    catch (Exception err) 
    { 
     //Write to the log. 
    } 
} 

J'espère que cette réponse sauvera quelqu'un de mal de tête: D Nous avons testé cela minutieusement et n'avons pas rencontré de problèmes.

Bonne codification!

+0

Juste curieux, dans la troisième approche, n'est pas le seul parallélisme que vous obtenez de Task.Factory.StartNew()? Même si vous utilisez Simple ExecuteNonQuery au lieu des variantes Begin et End, l'exécution sera effectivement la même, n'est-ce pas? Parce que vous attendez simplement après l'appel au BENQ. S'il vous plaît corrigez-moi si j'ai mal interprété quelque chose. – Quester

0

Dans ce cas, les instructions using ne seront pas nécessaires, car vous devrez le fermer vous-même manuellement plutôt que de laisser le sucre syntaxique le disposer pour vous (c'est-à-dire au }). Cela devrait être aussi simple que cela pour s'assurer que vous n'avez pas de fuite.

 
    using (SqlConnection sqlConnection = new SqlConnection("blahblah;Asynchronous Processing=true;") 
    { 
     using (SqlCommand command = new SqlCommand("someProcedureName", sqlConnection)) 
     { 
      sqlConnection.Open(); 
      command.CommandType = CommandType.StoredProcedure; 
      command.Parameters.AddWithValue("@param1", param1); 
      command.BeginExecuteNonQuery((ar) => 
      { 
       var cmd = (SqlCommand)ar.AsyncState; 
       cmd.EndExecuteNonQuery(ar); 
       cmd.Connection.Close(); 
      }, command); 
     } 
    } 
Comme vous pouvez voir l'expression lambda qui est déclenchée une fois la commande terminée (peu importe le temps que cela prendra), elle effectuera toute la fermeture pour vous.