2012-08-30 49 views
7

Estoy trabajando en una aplicación existente. Esta aplicación lee datos de un archivo enorme y luego, después de hacer algunos cálculos, almacena los datos en otra tabla.Bucle foreach muy lento

Pero el ciclo que hace esto (ver a continuación) está tomando mucho tiempo. Dado que el archivo a veces contiene miles de registros, todo el proceso lleva días.

¿Puedo reemplazar este foreach loop con algo más? Intenté usar Parallel.ForEach y me ayudó. Soy nuevo en esto, por lo que agradeceré su ayuda.

foreach (record someredord Somereport.r) 
{ 
    try 
    { 
     using (var command = new SqlCommand("[procname]", sqlConn)) 
     { 
      command.CommandTimeout = 0; 
      command.CommandType = CommandType.StoredProcedure; 
      command.Parameters.Add(…); 

      IAsyncResult result = command.BeginExecuteReader(); 
      while (!result.IsCompleted) 
      { 
       System.Threading.Thread.Sleep(10); 
      } 
      command.EndExecuteReader(result); 
     } 
    } 
    catch (Exception e) 
    { 
     … 
    } 
} 

Después de revisar las respuestas, eliminé el asíncrono y usados ​​editar el código de la siguiente manera. Pero esto no mejoró el rendimiento.

using (command = new SqlCommand("[sp]", sqlConn)) 
{ 
    command.CommandTimeout = 0; 
    command.CommandType = CommandType.StoredProcedure; 
    foreach (record someRecord in someReport.) 
    { 
     command.Parameters.Clear(); 
     command.Parameters.Add(....) 
     command.Prepare();        

     using (dr = command.ExecuteReader()) 
     { 
      while (dr.Read()) 
      { 
       if() 
       { 

       } 
       else if() 
       { 

       } 
      } 
     }        
    }       
} 
+7

Dos pensamientos: primero, está haciendo una asincronización incorrecta y, como resultado, es probable que esté durmiendo durante MUCHOS elementos en el ciclo. En segundo lugar, ¿puede reutilizar el objeto SqlCommand para todo el ciclo en lugar de crear/destruir uno cada vez? – n8wrl

+3

Si nos dice más acerca de qué es lo que está tratando de lograr, podríamos potencialmente mostrarle una solución en SQL que corre varios órdenes de magnitud más rápido, y evita todo este negocio asincrónico/paralelo por completo. –

+1

@ user1110790: El código que publicó estaba lleno de errores (y todavía tiene al menos uno), así que lo he limpiado un poco. Puedo sugerir humildemente que cuando publiques en SO, asegúrate de que tu código sea correcto; de lo contrario, es posible que obtenga muchos comentarios enfocados en eso, en lugar de en el tema real. – stakx

Respuesta

7

En lugar de bucle de la conexión SQL tantas veces, alguna vez la posibilidad de extraer todo el conjunto de datos fuera del servidor SQL y procesar los datos a través del conjunto de datos?

Editar: Decidido a explicar con más detalle lo que quise decir .. Usted puede hacer lo siguiente, pseudo código como sigue

  1. Usar un selecto * y obtener toda la información de la base de datos y almacenarlos en una lista de la clase o diccionario.
  2. Haga su foreach (grabe algunos Grabar en algún informe) y haga la coincidencia de condiciones como de costumbre.
+5

+1. Pero probablemente sea mejor cargar los datos en una colección fuertemente tipada y luego usar Linq en lugar de usar un DataSet. –

+0

Intenté usar un datase, pero por alguna razón se ralentizó aún más el proceso. También estamos registrando cada operación individual. ¿Crees que puedo hacer el registro en un subproceso separado para mejorar el rendimiento? – user1110790

+0

@ user1110790 - Según mi experiencia, los conjuntos de datos generalmente son lentos para trabajar. Es por eso que sugerí una colección fuertemente tipada. Simplemente trabajar con una colección IEnumerable en la memoria va a ser muy rápido. Y si estás haciendo muchas búsquedas clave, podría hacerse aún más rápido con un 'Diccionario'. –

6

Paso 1: Arranca el intento en async. No está implementado correctamente y está bloqueando de todos modos. Así que solo ejecuta el procedimiento y mira si eso ayuda.

Paso 2: Mueva el SqlCommand fuera del bucle y vuelva a utilizarlo para cada iteración. de esta forma, no incurre en el costo de crearlo y destruirlo para cada elemento de su ciclo.

Advertencia: Asegúrese de restablecer/borrar/eliminar los parámetros que no necesita de la iteración anterior. Hicimos algo como esto con parámetros opcionales y tuvimos 'purga' a través de la iteración anterior porque no limpiamos los parámetros que no necesitábamos.

+1

+1 El segundo paso es muy importante, mucha gente lo olvida. –

+0

El 'Paso 2: Mueva el SqlCommand fuera del lazo ...' es la mejoría importante !!! – Lester

3

Su mayor problema es que usted está bucle sobre esto:

IAsyncResult result = command.BeginExecuteReader(); 

while (!result.IsCompleted) 
{ 
    System.Threading.Thread.Sleep(10); 
} 

command.EndExecuteReader(result); 

Toda la idea del modelo asíncrono es que el subproceso de llamada (el que hace este bucle) debe estar girando seguridad de todos los asíncrono tareas usando el método Begin antes de comenzar a trabajar con los resultados con el método End. Si está utilizando Thread.Sleep() dentro de su hilo de llamada principal para esperar que se complete una operación asíncrona (como está aquí), lo está haciendo mal, y lo que termina sucediendo es que cada comando, uno a la vez , se lo está llamando y luego se espera antes de que comience el próximo.

lugar, trate de algo como esto:

public void BeginExecutingCommands(Report someReport) 
{ 
    foreach (record someRecord in someReport.r) 
    { 
     var command = new SqlCommand("[procname]", sqlConn); 

     command.CommandTimeout = 0; 
     command.CommandType = CommandType.StoredProcedure; 
     command.Parameters.Add(…); 

     command.BeginExecuteReader(ReaderExecuted, 
      new object[] { command, someReport, someRecord });     
    } 
} 

void ReaderExecuted(IAsyncResult result) 
{ 
    var state = (object[])result.AsyncState; 
    var command = state[0] as SqlCommand; 
    var someReport = state[1] as Report; 
    var someRecord = state[2] as Record; 

    try 
    { 
     using (SqlDataReader reader = command.EndExecuteReader(result)) 
     { 
      // work with reader, command, someReport and someRecord to do what you need. 
     } 
    } 
    catch (Exception ex) 
    { 
     // handle exceptions that occurred during the async operation here 
    } 
} 
+0

He eliminado el modificador de accesibilidad 'public' del método de devolución de llamada (' ReaderExecuted'). Esos no deberían ser públicos, ya que no son operaciones completas, sino solo el "resto" lógico de otro método. – stakx

+0

+1 para demostrar el uso correcto del patrón asíncrono 'Begin ...'/'End ...'. Sin embargo, no estoy 100% seguro de que esto solucionará el problema principal. Tampoco estoy seguro de qué tan bien el grupo de subprocesos y el DB lidiarán con posiblemente miles de solicitudes casi simultáneas ...? – stakx

+1

El grupo de subprocesos programará un cierto número de ellos tan rápido como ingresen, hasta un límite "mínimo". Luego, generará 4 subprocesos por segundo mientras el número de subprocesos está por encima de ese umbral, hasta que alcance un umbral máximo, en cuyo punto contendrá las nuevas solicitudes. Los umbrales mínimo y máximo son configurables. – KeithS

0

Parece ejecución de sus SQL pone comando bloquear en algunos recursos requeridos y esa es la razón forzadas a utilizar métodos Async (mi suposición).

Si la base de datos no está en uso, intente acceder de manera exclusiva a ella. Incluso cuando hay algunas transacciones internas debido a la complejidad del modelo de datos, considere consultar al diseñador de la base de datos.

+0

Gracias. La base de datos está siendo utilizada de hecho, así que no puedo hacer lo que sugirió. – user1110790

1

En SQL en el otro extremo de una escritura hay un (un) disco. Raramente puedes escribir más rápido en paralelo. De hecho, en paralelo a menudo lo ralentiza debido a la fragmentación del índice. Si puede ordenar los datos por clave primaria (en clúster) antes de cargarlos. En una gran carga, incluso deshabilitar otras teclas, cargar las claves de reconstrucción de datos.

No estoy seguro de qué está haciendo en el asynch, pero seguro que no estaba haciendo lo que esperaba, ya que estaba esperando en sí mismo.

try 
{ 
    using (var command = new SqlCommand("[procname]", sqlConn)) 
    { 
     command.CommandTimeout = 0; 
     command.CommandType = CommandType.StoredProcedure; 

     foreach (record someredord Somereport.r) 
     { 
      command.Parameters.Clear() 
      command.Parameters.Add(…); 

      using (var rdr = command.ExecuteReader()) 
      { 
       while (rdr.Read()) 
       { 
        … 
       } 
      } 
     } 
    } 
} 
catch (…) 
{ 
    … 
} 
+0

@stakx ¿Se encargará un rdr.Close() de eso? – Paparazzi

+0

IIRC 'rdr.Close()' tendría el mismo efecto que 'rdr.Dispose()' ... pero un bloque 'using' es más fácil que envolver' rdr.Close() 'en una cláusula' finally' (que tendrías que hacer por seguridad de excepción). – stakx

+0

OK, pero a la cabeza de crear un nuevo rdr cada ciclo. ¿Qué excepción no se detectaría en Catch si se reutilizara rdr? – Paparazzi

1

Como estábamos hablando en los comentarios, el almacenamiento de estos datos en la memoria y trabajar con él puede haber un enfoque más eficiente.

Así que una manera fácil de hacerlo es comenzar con Entity Framework. Entity Framework generará automáticamente las clases en función de su esquema de base de datos. Entonces puede import a stored procedure que contiene su instrucción SELECT. La razón por la que sugiero importar un proceso almacenado en EF es que este enfoque generalmente es más eficiente que hacer consultas en LINQ contra EF.

continuación, ejecute el procedimiento almacenado y almacenar los datos en un List así ...

var data = db.MyStoredProc().ToList();

entonces usted puede hacer lo que quiera con que data. O como he mencionado, si está haciendo una gran cantidad de operaciones de búsqueda de las claves primarias a continuación, utilizar ToDictionary() algo como esto ...

var data = db.MyStoredProc().ToDictionary(k => k.MyPrimaryKey);

De cualquier manera, usted estará trabajando con su data en la memoria en este punto.