2010-10-24 3 views
3

J'ai besoin de conseils concernant une application que j'ai écrite. Les problèmes que j'ai sont dû à ma DAL et aux connexions à ma base de données SQL Server 2008 n'étant pas fermées, cependant j'ai regardé mon code et chaque connexion est toujours fermée.C# Application multithread et connexions SQL help

L'application est une application multithread qui récupère un ensemble d'enregistrements et pendant qu'elle traite un enregistrement, elle met à jour les informations à son sujet.

Voici le flux:

L'administrateur a la possibilité de définir le nombre de threads à exécuter et le nombre d'enregistrements par thread pour tirer.

Voici le code qui fonctionne après avoir cliqué sur démarrer:

adaptateurs sont des abstractions à mon DAL est un échantillon ici de ce qu'ils ressemblent:

public class UserDetailsAdapter: IDataAdapter<UserDetails> 
{ 
    private IUserDetailFactory _factory; 

     public UserDetailsAdapter() 
     { 
      _factory = new CampaignFactory(); 
     } 

     public UserDetails FindById(int id){ 
      return _factory.FindById(id); 
     } 
} 

Dès que le _factory est appelé traite le SQL et ferme immédiatement la connexion.

code pour App Fileté:

private int _recordsPerthread; 


private int _threadCount; 

    public void RunDetails() 
    { 
     //create an adapter instance that is an abstration 
     //of the data factory layer 
     var adapter = new UserDetailsAdapter(); 

     for (var i = 1; i <= _threadCount; i++) 
     { 
      //This adater makes a call tot he databse to pull X amount of records and 
      //set a lock filed so the next set of records that are pulled are differnt. 
      var details = adapter.FindTopDetailsInQueue(_recordsPerthread); 
      if (details != null) 
      { 
       var parameters = new ArrayList {i, details}; 
       ThreadPool.QueueUserWorkItem(ThreadWorker, parameters); 
      } 
      else 
      { 
       break; 
      } 
     } 
    } 

    private void ThreadWorker(object parametersList) 
    { 
     var parms = (ArrayList) parametersList; 
     var threadCount = (int) parms[0]; 
     var details = (List<UserDetails>) parms[1]; 
     var adapter = new DetailsAdapter(); 


     //we keep running until there are no records left inthe Database 
     while (!_noRecordsInPool) 
     { 
      foreach (var detail in details) 
      { 
       var userAdapter = new UserAdapter(); 
       var domainAdapter = new DomainAdapter(); 

       var user = userAdapter.FindById(detail.UserId); 
       var domain = domainAdapter.FindById(detail.DomainId); 

       //...do some work here...... 

       adapter.Update(detail); 
      } 

      if (!_noRecordsInPool) 
      { 
       details = adapter.FindTopDetailsInQueue(_recordsPerthread); 


       if (details == null || details.Count <= 0) 
       { 
        _noRecordsInPool = true; 
        break; 
       } 
      } 
     } 
    } 

Les accidents d'application, car il semble y avoir des problèmes de connexion à la base de données. En regardant dans mes fichiers journaux pour le DAL je vois ceci:

Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached

Quand je lance ce dans un thread, il fonctionne très bien. Je devine quand je cours son dans plusieurs fils je fais évidemment trop de connexions à la DB. Toute réflexion sur la façon dont je peux continuer à courir dans plusieurs threads et assurez-vous que la base de données ne me donne pas d'erreurs.

Mise à jour: Je pense que mes problèmes peuvent être des blocages dans ma base de données. Voici le code dans SQL qui exécute whe je reçois une erreur de blocage:

WITH cte AS ( 
    SELECT TOP (@topCount) * 
    FROM 
    dbo.UserDetails WITH (READPAST) 
WHERE 
    dbo.UserDetails where IsLocked = 0) 

UPDATE cte 
    SET 
    IsLocked = 1 

    OUTPUT INSERTED.*; 

Je n'ai jamais eu des problèmes avec ce code avant (dans d'autres applications). J'ai réorganisé mes index car ils étaient fragmentés à 99%. Cela n'a pas aidé. Je suis à perte ici.

Répondre

0

Eh bien, après avoir fait quelques recherches, j'ai trouvé qu'il pourrait y avoir un bogue dans SQL Server 2008 et exécuter des requêtes parallèles. Je vais devoir déterrer le lien où j'ai trouvé la discussion sur ce point, mais je me suis retrouvé en cours d'exécution sur mon serveur:

sp_configure 'max degree of parallelism', 1; GO RECONFIGURE WITH OVERRIDE; GO

Cela peut diminuer les performances de votre serveur, dans l'ensemble, il ne peut pas être une option pour certaines personnes, mais cela a bien fonctionné pour moi.

Pour certaines requêtes, j'ai ajouté l'option MAXDOP (n étant le nombre de processeurs à utiliser) afin qu'ils puissent fonctionner plus efficacement. Cela m'a aidé un peu. Deuxièmement, j'ai découvert que la méthode Dispose de ma couche DAL utilisait la méthode GC.Suppressfinalize. Donc, mes sections finales ne tiraient pas correctement dans mon DAL et ne fermaient pas mes connexions. Merci à tous ceux qui ont donné leur avis!

3

Je suis confus quant à l'endroit où vos connexions de code obtenir ouvert, mais vous voulez probablement vos adaptateurs de données pour mettre en œuvre IDispose (en prenant soin de fermer la connexion de la piscine que vous quittez la portée using) et envelopper votre code dans using blocs:

using (adapter = new UserDetailsAdapter()) 
{ 
    for (var i = 1; i <= _threadCount; i++) 
    { 
     [..] 
    } 
} // adapter leaves scope here; connection is implicitly marked as no longer necessary 

ADO.NET utilise la mise en commun de connexion, il n'y a donc pas besoin de (et il peut être contre-productif) les connexions explicitement ouvrir et fermer.

+0

C'est une bonne idée .... – DDiVita

+0

L'appel '.Close()' ne ferme pas réellement la connexion quand le pooling est en cours d'utilisation. L'appel '.Dispose()' ne ferme pas non plus la connexion. La connexion est libérée dans le pool en appelant '.Close()' ou '.Dispose()' ('.Dispose()' appellera '.Close()' s'il n'est pas appelé avant d'être éliminé). TOUJOURS appeler '.Dispose()' (préférablement en utilisant une instruction using), et éventuellement appeler '.Close()' si la fantaisie vous prend. – Mark

+0

C'est ce que je voulais dire par "il n'y a pas besoin de les fermer explicitement" (je ne savais pas que '.Close()' * still * ne les fermerait pas explicitement, cependant). Le bloc 'using' s'occupera de' .Dispose() 'une fois qu'il aura quitté la portée, et un DataAdapter devrait s'ouvrir implicitement. –

0

Je ne sais pas comment vous vous connectez réellement à la base de données. L'adaptateur doit référencer une connexion.

Comment initialisez-vous réellement cette connexion?

Si vous utilisez un nouvel adaptateur pour chaque thread, vous devez utiliser une nouvelle connexion pour chaque adaptateur.

Je ne connais pas très bien votre environnement, mais je suis certain que vous avez vraiment besoin de beaucoup de connexions ouvertes avant que votre base de données commence à s'en plaindre!

+0

Les adaptateurs sont juste des abstractions de mes couches d'usine. Les usines, pour chaque appel, ouvrent une connexion, font le travail, puis ferment les connexions. Ainsi, chaque appel peut ouvrir une nouvelle connexion à la base de données. – DDiVita