2012-07-27 6 views
2

Mon problème concerne un serveur Web aspx.net 4.0 bloquant sous une charge accrue. En bloquant je veux dire que la demande est envoyée par le client mais la réponse est renvoyée après ~ 45 secondes. Ceci est reproductible dans l'environnement de développement et de production. Ces 45 secondes semblent être constantes et j'ai mesuré à la fois sur le client et dans la page aspx entre l'appel du constructeur() et void Render(HtmlTextWriter writer). J'utilise à la fois plusieurs SqlDataSources et des contrôles personnalisés en utilisant 6 SqlCommand.BeginExecuteReader(...) au total sur une page. Je peux éliminer le problème si je désactive les contrôles avec le motif BeginExecuteReader/EndExecuteReader. Je suppose donc que l'un des appels BeginExecute est bloqué jusqu'à ce qu'un thread soit disponible dans le ThreadPool.threadpool famine

imprimer des messages de débogage et reconnu un modèle où toujours un tas de messages de sortie de fil est imprimé juste befor la demande bloquée est renvoyée:

Le fil « GetMolFileAsync » (0x1ba4) est sorti avec le code 0 (0x0).

Le thread 'GetMolFileAsync' (0x27d0) s'est terminé avec le code 0 (0x0).

Le thread '' (0x23c) s'est terminé avec le code 0 (0x0).

Le thread 'GetCompoundDepositionInfo' (0x1e88) s'est terminé avec le code 0 (0x0).

Le thread 'GetMolFileAsync' (0x2758) s'est terminé avec le code 0 (0x0).

0x43 27/07/2012 15:09:42 45 ==>fil bloqué a pris 45 secondes

0x5F 27/07/2012 15:10:27 0 ==>comportement normal, traité dans certains miliseconds

...

Ceci est la méthode pour lancer une requête à la base de données

public static IAsyncResult GetCompoundDepositionInfoAsync(object sender, EventArgs e, AsyncCallback callback, object state) 
    { 
     GetCompoundVersionInfoAsyncParameters parameters = (GetCompoundVersionInfoAsyncParameters)state; 
     IAsyncResult res = null; 

     parameters.cmd = new System.Data.SqlClient.SqlCommand("www.GetCompoundDepositionInfo", new System.Data.SqlClient.SqlConnection(parameters.connectionstring)); 
     parameters.cmd.CommandType = System.Data.CommandType.StoredProcedure; 
     parameters.cmd.Parameters.AddWithValue("@CompoundID", parameters.CompoundID); 
     try 
     { 
      parameters.cmd.Connection.Open(); 
      res = parameters.cmd.BeginExecuteReader(callback, parameters, System.Data.CommandBehavior.CloseConnection); 
     } 
     catch (Exception ex) 
     { 
      if (parameters.cmd.Connection.State == System.Data.ConnectionState.Open) 
      { 
       parameters.cmd.Connection.Close(); 
      } 
      throw new Exception("Exception in calling GetCompoundDepositionInfoAsync()", ex); 
     } 
     return res; 
    } 

c'est la fonction de rappel

public void GetCompoundDepositionInfoCallback(IAsyncResult result) 
    { 
     gmdTools.GmdCompound.GetCompoundVersionInfoAsyncParameters param = (gmdTools.GmdCompound.GetCompoundVersionInfoAsyncParameters)result.AsyncState; 

     System.Threading.Thread.CurrentThread.Name = "GetCompoundDepositionInfo"; 
     using(System.Data.SqlClient.SqlCommand command = param.cmd) 
     using(System.Data.SqlClient.SqlDataReader reader = command.EndExecuteReader(result)) 
     { 
      try 
      { 
       if (reader.Read()) 
       { 
        lblDeposited.Text = string.Concat("at ", reader.GetDateTime(0).ToShortDateString(), " by ", reader.GetString(1)); 
       } 
      } 
      finally 
      { 
       if (reader != null) 
       { 
        reader.Close(); 
        command.Connection.Close(); 
       } 
      } 
     } 
    } 

et c'est le code pour les coller ensemble ...

Page.RegisterAsyncTask(new PageAsyncTask(
       new BeginEventHandler(gmdTools.GmdCompound.GetCompoundLastChangeInfoAsync) 
       , new EndEventHandler(GetCompoundLastChangeInfoCallback) 
       , new EndEventHandler(GetCompoundInfoAsyncTimeout) 
       , new gmdTools.GmdCompound.GetCompoundVersionInfoAsyncParameters() 
       { 
        connectionstring = Properties.Settings.Default.GmdConnectionString, 
        CompoundID = CompoundId, 
       }, true 
      )); 

Comme je l'ai déjà passé des heures à regarder ce code je serais reconnaissants des commentaires.

MISE À JOUR Ce 45 secondes sont motivées par le défaut Page.AsyncTimeout et peuvent être modifiés à 10 secondes en utilisant les Async="true" AsyncTimeout="10" déclarations. Bien que j'aie beaucoup amélioré les performances globales du site en ajoutant des index appropriés, très souvent le client doit attendre ce laps de temps avant que le serveur n'envoie la réponse. Dans ce cas, aucun gestionnaire AsyncTimeout n'est appelé. Je suppose que la page enregistre toutes les opérations asynchrones mais ne reconnaît finalement pas que certaines des opérations asynchrones ont réussi et attend donc AsyncTimeout secondes avant de rendre la page. Des commentaires à ce sujet?

+0

Ressemble de blocage sur la base de données. Je vous suggère de surveiller l'activité de la base de données/utiliser SQL Profiler pour savoir ce qui se passe dans la base de données. – Polyfun

+0

Êtes-vous sûr que les requêtes de base de données ne sont pas le coupable ici? Le fait que le moment est toujours le même pour un certain nombre de clients suggère que le facteur commun peut être la base de données. – dash

+0

@ShellShock: Merci d'avoir commenté ma question! Compte tenu de votre expérience, quels événements dans le profileur SQL suggérez-vous de suivre ce blocage? – jahu

Répondre

0

C'est probablement la base de données, étant donné le choix ou les lignes de la requête renvoie, est-il en choisissant plus ou moins d'un sur un millier? MS SQL va changer la façon dont il fonctionne lorsque le choix passe par 1 sur 1000 lignes retournées. Si vous exécutez la requête à l'aide de SQL Profileer, obtenez-vous une analyse de table? Si vous exécutez le sp intégré pour déterminer les index manquants, renvoie-t-il les demandes d'index sur ces tables? Vos statistiques sont-elles à jour? En fonction de cela, une sauvegarde restaurée exécute la requête rapidement, car lorsque vous restaurez une sauvegarde, les statistiques sont mises à jour. Existe-t-il un index clusterisé sur (toutes/toutes) vos tables?

De plus, cette réponse pourrait être pertinente Entity Framework MVC Slow Page Loads

+0

J'ai optimisé certaines requêtes en ajoutant des index appropriés qui ont conduit à une amélioration globale. Cependant, le blocage se produit encore quelques fois. D'autre part, j'utilise maintenant 'System.Threading.ThreadPool.GetAvailableThreads (sur AvailableWorkerThreads, out AvailablePortThreads)' et 'System.Threading.ThreadPool.GetMaxThreads (out MaxWorkerThreads, out MaxPortThreads)' pour vérifier que j'ai beaucoup de 2.400 PortThreads et 2 379 WorkerThreads disponibles. Merci à tous d'avoir porté ceci à mon attention. – jahu

0

Utilisez-vous la propriété async=true dans le connectionstring. Ceci est requis pour les opérations asynchrones réelles utilisant le SqlClient. Si possible, pouvez-vous essayer cela sur .Net 4.5 avec la fonction asynchrone des tâches avec le code ci-dessous.

public async Task GetCompoundDepositionInfoAsync(CancellationToken cancellationToken) 
{ 
    parameters.cmd = new System.Data.SqlClient.SqlCommand("www.GetCompoundDepositionInfo", new System.Data.SqlClient.SqlConnection(parameters.connectionstring)); 
    parameters.cmd.CommandType = System.Data.CommandType.StoredProcedure; 
    parameters.cmd.Parameters.AddWithValue("@CompoundID", parameters.CompoundID); 
    using (var connection = new SqlConnection(parameters.connectionstring)) 
    using (var command = new SqlCommand(query, connection)) 
    { 
     await connection.OpenAsync(cancellationToken); 
     using (var reader = await command.ExecuteReaderAsync(cancellationToken)) 
     { 
      if (await reader.ReadAsync(cancellationToken)) 
      { 
       lblDeposited.Text = string.Concat("at ", reader.GetDateTime(0).ToShortDateString(), " by ",  reader.GetString(1)); 
      } 
     } 
    } 
} 

et page_load()

RegisterAsyncTask(new PageAsyncTask(GetCompoundDepositionInfoAsync)); 
+0

J'utilise 'async = true' dans la chaîne de connexion. – jahu