2012-06-28 19 views
10

Intenté que el título fuera lo más específico posible. Básicamente lo que he ejecuta dentro de un hilo BackgroundWorker ahora es un código que se parece a:Cómo realizar una consulta SQL a la operación de DataTable que puede cancelarse

SqlConnection conn = new SqlConnection(connstring); 
        SqlCommand cmd = new SqlCommand(query, conn); 
        conn.Open(); 
        SqlDataAdapter sda = new SqlDataAdapter(cmd); 
        sda.Fill(Results); 
        conn.Close(); 
        sda.Dispose(); 

Cuando la consulta es una cadena que representa un gran, tiempo de consulta, y conn es el objeto de conexión.

Mi problema ahora es que necesito un botón de detención. Me di cuenta de que matar al backgroundworker sería inútil porque aún quiero conservar los resultados que quedan después de que se cancela la consulta. Además, no podría verificar el estado cancelado hasta después de la consulta.

Lo que he encontrado hasta el momento:

He estado tratando de conceptualizar cómo manejar esto de manera eficiente sin tener demasiado grande de un impacto en el rendimiento.

Mi idea era utilizar un SqlDataReader para leer los datos de la pieza de consulta a la vez para que tuviera un "bucle" para verificar un indicador que podía establecer desde la GUI mediante un botón. El problema es que, por lo que sé, no puedo usar el método Load() de una tabla de datos y todavía puedo cancelar el comando sql. Si me equivoco, por favor avíseme porque eso facilitaría la cancelación.

A la luz de lo que he descubierto que llegué a la conclusión de que sólo puede ser capaz de cancelar la mitad de consulta SqlCommand si hice algo como lo de abajo (pseudo-código):

while(reader.Read()) 
{ 
//check flag status 
//if it is set to 'kill' fire off the kill thread 

//otherwise populate the datatable with what was read 
} 

Sin embargo, Me parece que esto sería altamente ineficaz y posiblemente costoso. ¿Es esta la única manera de matar un comando sql en progreso que absolutamente debe estar en una tabla de datos? ¡Cualquier ayuda sería apreciada!

+1

¡una pregunta muy decente! – Adi

+0

Matar un hilo nunca es una buena idea. ¿Has probado 'cmd.Cancel();'? –

+0

¿Por qué crees que usar DataReader sería costoso? – Habib

Respuesta

5

En realidad, hay dos etapas en las materias cancelación:

  1. Cancelación de la ejecución de la consulta inicial antes de las primeras filas son devueltos
  2. Abortar el proceso de leer las filas, ya que se sirven

Dependiendo de la naturaleza de la instrucción SQL real, eithe r de estos pasos podría ser el 99% del tiempo, por lo que ambos deberían considerarse. Por ejemplo, llamar al SELECT * en una tabla con mil millones de filas no llevará esencialmente tiempo a ejecutar, pero llevará mucho tiempo leerlo. Por el contrario, solicitar una unión súper complicada en tablas pobremente ajustadas y luego envolver todo en algunas cláusulas de agregación puede llevar minutos en ejecutarse, pero el tiempo es insignificante para leer el puñado de filas una vez que realmente se devuelven.

Los motores de base de datos avanzados bien ajustados también almacenarán en caché trozos de filas a la vez para consultas complicadas, por lo que verá pausas alternas donde el motor está ejecutando la consulta en el siguiente lote de filas y luego ráfagas rápidas de datos devuelve el siguiente lote de resultados.

Cancelación de la ejecución de la consulta

Con el fin de ser capaz de cancelar una consulta mientras se está ejecutando puede utilizar una de las sobrecargas de SqlCommand.BeginExecuteReader para iniciar la consulta, y llamar a SqlCommand.Cancel a abortar. Alternativamente, puede llamar a ExecuteReader() sincrónicamente en un hilo y aún llamar a Cancelar() desde otro. No incluyo ejemplos de código porque hay muchos en la documentación.

Interrumpir la operación de lectura

Aquí usando un simple indicador booleano es probablemente la manera más fácil. Y recuerde que es muy fácil de llenar una fila de la tabla de datos utilizando la sobrecarga de Rows.Add() que toma una matriz de objeto, es decir:

object[] buffer = new object[reader.FieldCount] 
while(reader.Read()) { 
    if(cancelFlag) break; 
    reader.GetValues(buffer); 
    dataTable.Rows.Add(buffer); 
} 

Cancelación de bloqueo de llamadas para leer()

Un Una especie de caso mixto ocurre cuando, como se mencionó anteriormente, una llamada a reader.Read() hace que el motor de la base de datos haga otro lote de procesamiento intensivo. Como se indica en la documentación de MSDN, las llamadas al Read() pueden estar bloqueando en este caso, incluso si la consulta original se ejecutó con BeginExecuteReader. Todavía puede evitar esto llamando al Read() en un hilo que maneja toda la lectura pero llama al Cancel() en otro hilo. La manera de saber si el lector está en una Read bloqueo de llamadas es tener otra bandera las actualizaciones de hilo lector mientras que el subproceso de supervisión lee:

... 
inRead = true 
while(reader.Read()) { 
    inRead = false 
    ... 
    inRead = true 
} 

// Somewhere else: 
private void foo_onUITimerTick(...) { 
    status.Text = inRead ? "Waiting for server" : "Reading"; 
} 

En cuanto al rendimiento del lector vs adaptador

un DataReader generalmente es más rápido que con DataAdapter.Fill(). El objetivo de un DataReader es ser muy, muy rápido y receptivo para leyendo. Verificar una bandera booleana una vez por fila no agregaría una diferencia mensurable en el tiempo incluso en millones de filas. El factor limitante para una gran consulta de base de datos no es el tiempo de procesamiento local de la CPU, sino el tamaño del conducto de E/S (su conexión de red para una base de datos remota o la velocidad de disco para una local) o una combinación de la La velocidad del disco del servidor db y el tiempo de procesamiento de la CPU para una consulta compleja. Tanto un DataAdapter como un DataReader pasarán tiempo (tal vez la mayoría de las veces) esperando solo unos nanosegundos a la vez para que se sirva la siguiente fila.

Una comodidad de DataAdapter.Fill() es que lo hace la magia de la generación dinámica de las columnas de DataTable para que coincida con los resultados de la consulta, pero eso no es difícil de hacer usted mismo (ver SqlDataReader.GetSchemaTable()).

+0

Sí, definitivamente es una parte desagradable que fue asegurarse de que todo estaba formateado correctamente cuando lo puse en la tabla de datos. Aprendí un TONTO de esta publicación. –

+0

Aparentemente, los comentarios no se pueden editar después de un cierto límite de tiempo. Voy a sentarme en esta publicación y pensar en ello por un tiempo. Si tengo más preguntas, preguntaré. Si no, lo marcaré como respondido. ¡Gracias! –

0

Sólo una oportunidad

Yo sugeriría que coloques una consulta mucho tiempo en un BackgroundWorker y pasar el comando a ella. para que pueda mantener el objeto de comando bajo control. Cuando cancela comando viene, simplemente decir, introducidos (a BackgroundWorker cuales en curso) comando para cancelar por command.Cancel()

Cuestiones relacionadas