2012-06-28 9 views
10

Ho cercato di rendere il titolo più specifico possibile. Fondamentalmente quello che ho in esecuzione all'interno di un filo BackgroundWorker ora è un codice che assomiglia:Come eseguire una query SQL sull'operazione DataTable che può essere annullata

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(); 

Dove query è una stringa che rappresenta un grande, in termini di tempo query e conn è l'oggetto di connessione.

Il mio problema ora è che ho bisogno di un pulsante di arresto. Sono giunto alla conclusione che uccidere chi lavora in background sarebbe inutile perché voglio mantenere i risultati che rimangono dopo che la query è stata cancellata. Inoltre, non sarebbe in grado di controllare lo stato annullato fino a dopo la query.

Quello che mi è venuta in mente finora:

Ho cercato di concettualizzare come gestire in modo efficiente senza prendere troppo grande di un calo di prestazioni.

La mia idea era di utilizzare un SqlDataReader per leggere i dati dal pezzo di query alla volta in modo che avessi un "ciclo" per controllare un flag che potevo impostare dalla GUI tramite un pulsante. Il problema è che, per quanto so, non posso usare il metodo Load() di un datatable ed essere ancora in grado di cancellare lo sqlcommand. Se sbaglio, per favore fammelo sapere perché ciò renderebbe l'annullamento un po 'più facile.

Alla luce di quello che ho scoperto sono venuto alla realizzazione io possa essere in grado di annullare il SqlCommand mid-query solo se ho fatto qualcosa di simile al di sotto (pseudo-codice):

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 
} 

Tuttavia, mi sembra che sarebbe molto inefficace e probabilmente costoso. È questo l'unico modo per uccidere uno sqlcommand in corso che deve assolutamente essere in un datatable? Qualsiasi aiuto sarebbe apprezzato!

+1

una domanda molto buona! – Adi

+0

Uccidere un thread non è mai una buona idea. Hai provato 'cmd.Cancel();'? –

+0

Perché pensi che l'uso di DataReader sarebbe costoso? – Habib

risposta

5

Ci sono davvero due fasi in cui materia cancellazione:

  1. Annullamento l'esecuzione iniziale query prima le prime righe vengono restituiti
  2. Interruzione del processo di lettura delle righe come sono serviti

a seconda della natura del reale dichiarazione di sql, eithe r di questi passaggi potrebbe essere il 99% delle volte, quindi entrambi dovrebbero essere considerati. Ad esempio, chiamando lo SELECT * su un tavolo con un miliardo di righe, non ci vorrà assolutamente tempo per eseguire ma ci vorrà molto tempo per leggere. Viceversa, la richiesta di un join super complicato su tabelle scarsamente sintonizzate e quindi l'elaborazione di tutto in alcune clausole di aggregazione può richiedere alcuni minuti, ma un tempo trascurabile per leggere la manciata di righe una volta che vengono effettivamente restituite.

I motori di database avanzati ottimizzati registrano inoltre blocchi di righe alla volta per query complesse, quindi verranno visualizzate pause alternate in cui il motore esegue la query sul successivo gruppo di righe e quindi scoppi rapidi di dati mentre restituisce il prossimo lotto di risultati.

Annullamento l'esecuzione della query

Al fine di essere in grado di annullare una query mentre è in esecuzione è possibile utilizzare uno dei sovraccarichi di SqlCommand.BeginExecuteReader per avviare la query e chiamare SqlCommand.Cancel ad abortire. In alternativa, è possibile chiamare ExecuteReader() in modo sincrono in un thread e comunque chiamare Cancel() da un altro. Non includo esempi di codice perché ce ne sono molti nella documentazione.

Interruzione l'operazione di lettura

qui utilizzando un semplice flag booleano è probabilmente il modo più semplice. E ricordatevi che è veramente facile da riempire una riga della tabella di dati utilizzando il sovraccarico Rows.Add(), che accetta un array di oggetti, vale a dire:

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

Annullamento di bloccare le chiamate a Read()

A una sorta di caso misto si verifica quando, come menzionato in precedenza, una chiamata a reader.Read() fa sì che il motore di database esegua un altro batch di elaborazione intensiva. Come indicato nella documentazione MSDN, le chiamate a Read() possono essere bloccate in questo caso anche se la query originale è stata eseguita con BeginExecuteReader. È ancora possibile aggirare questo chiamando Read() in un thread che gestisce tutte le letture ma chiama Cancel() in un'altra discussione. Il modo in cui si sa se il lettore si trova in un blocco Read chiamata è di avere un'altra bandiera gli aggiornamenti thread di lettura, mentre il thread di monitoraggio si legge:

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

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

Per quanto riguarda le prestazioni di Reader vs adattatore

un DataReader è solitamente più veloce rispetto all'utilizzo di DataAdapter.Fill(). L'intero punto di un DataReader deve essere davvero, molto veloce e reattivo per leggendo. Il controllo di alcune flag booleane una volta per riga non aggiungerebbe una differenza misurabile nel tempo anche su milioni di righe.

Il fattore limitante per una query di database grande non è il tempo di elaborazione della CPU locale ma la dimensione della pipe I/O (la connessione di rete per un database remoto o la velocità del disco per uno locale) o una combinazione del la velocità del disco del server db e il tempo di elaborazione della CPU per una query complessa. Sia un DataAdapter che un DataReader trascorreranno del tempo (forse la maggior parte del tempo) aspettando solo pochi nanosecondi alla volta per la prossima riga da servire.

Una comodità di DataAdapter.Fill() è che fa la magia di generare dinamicamente le colonne DataTable in modo che corrispondano ai risultati dell'interrogazione, ma non è difficile farlo da soli (vedere SqlDataReader.GetSchemaTable()).

+0

Sì, quella è sicuramente una parte che era scoraggiante stava facendo in modo che tutto fosse formattato correttamente quando lo metto nel datatable. Ho imparato un TON da questo post. –

+0

Apparentemente i commenti non possono essere modificati dopo un certo limite di tempo. Mi siederò su questo post e ci penserò per un po '. Se avessi altre domande, chiederò. Se no, segnerò risposta. Grazie! –

0

Basta una prova

vorrei suggerire di mettere un tempo di query in un BackgroundWorker e passare il comando ad esso. in modo da poter tenere il comando oggetto in controllo. Quando annullare comando viene, basta dire passato (a BackgroundWorker che in corso) comando per annullare da command.Cancel()

Problemi correlati