2016-02-13 15 views
5

Sto cercando il modo corretto per gestire più chiamate di database che potrebbero trarre beneficio dall'esecuzione simultanea. Le query sono solo per stored procedure che eseguono inserimenti o fusioni utilizzando dati assemblati a livello di codice in DataTable nella mia app ASP.NET MVC.Come effettuare correttamente chiamate al database asincrono/parallelo

Naturalmente ho visto alcune informazioni su async e await, e sembra essere quello che avrei bisogno di fare, ma non ho una chiara comprensione di come implementarlo. Alcune informazioni dicono che le chiamate sarebbero ancora sequenziali e che si sarebbe ancora in attesa di un'altra per completare. Sembra inutile.

In definitiva, vorrei una soluzione che mi consenta di eseguire tutte le query nel tempo necessario per completare la procedura più lunga. Vorrei che tutte le query restituissero il numero di record interessati (come fanno ora).

Ecco quello che hanno in corso la società (che non è in alcun modo parallelo):

// Variable for number of records affected 
var recordedStatistics = new Dictionary<string, int>(); 

// Connect to the database and run the update procedure 
using (var dbc = new SqlConnection(db.Database.Connection.ConnectionString)) 
{ 
    dbc.Open(); 

    // Merge One procedure 
    using (SqlCommand cmd = new SqlCommand("MergeOneProcedure", dbc)) 
    { 
     // 5 minute timeout on the query 
     cmd.CommandTimeout = 300; 
     cmd.CommandType = CommandType.StoredProcedure; 
     cmd.Parameters.AddWithValue("@TVP", MergeOneDataTable); 

     // Execute procedure and record the number of affected rows 
     recordedStatistics.Add("mergeOne", cmd.ExecuteNonQuery()); 
    } 

    // Merge Two procedure 
    using (SqlCommand cmd = new SqlCommand("MergeTwoProcedure", dbc)) 
    { 
     // 5 minute timeout on the query 
     cmd.CommandTimeout = 300; 
     cmd.CommandType = CommandType.StoredProcedure; 
     cmd.Parameters.AddWithValue("@TVP", MergeTwoDataTable); 

     // Execute procedure and record the number of affected rows 
     recordedStatistics.Add("mergeTwo", cmd.ExecuteNonQuery()); 
    } 

    // Merge Three procedure 
    using (SqlCommand cmd = new SqlCommand("MergeThreeProcedure", dbc)) 
    { 
     // 5 minute timeout on the query 
     cmd.CommandTimeout = 300; 
     cmd.CommandType = CommandType.StoredProcedure; 
     cmd.Parameters.AddWithValue("@TVP", MergeThreeDataTable); 

     // Execute procedure and record the number of affected rows 
     recordedStatistics.Add("mergeThree", cmd.ExecuteNonQuery()); 
    } 

    // Merge Four procedure 
    using (SqlCommand cmd = new SqlCommand("MergeFourProcedure", dbc)) 
    { 
     // 5 minute timeout on the query 
     cmd.CommandTimeout = 300; 
     cmd.CommandType = CommandType.StoredProcedure; 
     cmd.Parameters.AddWithValue("@TVP", MergeFourDataTable); 

     // Execute procedure and record the number of affected rows 
     recordedStatistics.Add("mergeFour", cmd.ExecuteNonQuery()); 
    } 

    // Merge Five procedure 
    using (SqlCommand cmd = new SqlCommand("MergeFiveProcedure", dbc)) 
    { 
     // 5 minute timeout on the query 
     cmd.CommandTimeout = 300; 
     cmd.CommandType = CommandType.StoredProcedure; 
     cmd.Parameters.AddWithValue("@TVP", MergeFiveDataTable); 

     // Execute procedure and record the number of affected rows 
     recordedStatistics.Add("mergeFive", cmd.ExecuteNonQuery()); 
    } 

    dbc.Close(); 
} 

return recordedStatistics; 

Tutto questo codice è all'interno dello stesso metodo che assembla i dati per le DataTable. La mia comprensione limitata di async mi porterebbe a credere che avrei bisogno di estrarre il codice precedente nel suo metodo. Quindi chiamerei quel metodo e await il ritorno. Tuttavia, non ne so nemmeno abbastanza per iniziare.

Non ho mai eseguito alcuna codifica asincrona/parallela/multithreading prima. Questa situazione mi fa sentire come se fosse il momento perfetto per entrare. Detto questo, mi piacerebbe imparare il modo migliore, invece di dover disimparare nel modo sbagliato.

risposta

7

Ecco un esempio di come si dovrebbe fare:

Ecco, io creo due metodi per avvolgere due operazioni, è necessario fare lo stesso per le altre operazioni:

public async Task<int> MergeOneDataTableAsync() 
{ 
    // Merge One procedure 
    using (SqlCommand cmd = new SqlCommand("MergeOneProcedure", dbc)) 
    { 
     // 5 minute timeout on the query 
     cmd.CommandTimeout = 300; 
     cmd.CommandType = CommandType.StoredProcedure; 
     cmd.Parameters.AddWithValue("@TVP", MergeOneDataTable); 

     return await cmd.ExecuteNonQueryAsync().ConfigureAwait(false); 
    } 
} 


public async Task<int> MergeTwoDataTableAsync() 
{ 
    // Merge Two procedure 
    using (SqlCommand cmd = new SqlCommand("MergeTwoProcedure", dbc)) 
    { 
     // 5 minute timeout on the query 
     cmd.CommandTimeout = 300; 
     cmd.CommandType = CommandType.StoredProcedure; 
     cmd.Parameters.AddWithValue("@TVP", MergeTwoDataTable); 

     return await cmd.ExecuteNonQueryAsync().ConfigureAwait(false); 
    } 
} 

Si noti che Sto usando il metodo ExecuteNonQueryAsync per eseguire la query.

E poi il tuo metodo originale sarebbe simile a questa:

using (var dbc = new SqlConnection(db.Database.Connection.ConnectionString)) 
{ 
    dbc.Open(); 

    Task<int> task1 = MergeOneDataTableAsync(); 
    Task<int> task2 = MergeTwoDataTableAsync(); 

    Task.WaitAll(new Task[]{task1,task2}); //synchronously wait 

    recordedStatistics.Add("mergeOne", task1.Result); 
    recordedStatistics.Add("mergeTwo", task2.Result); 
} 

prega di notare che sto mantenendo questo metodo sincrono. Un'altra opzione (in realtà uno migliore) è quello di convertire il metodo in un uno asincrono come questo:

public async Task<Dictionary<string, int>> MyOriginalMethod() 
{ 
    //... 
    using (var dbc = new SqlConnection(db.Database.Connection.ConnectionString)) 
    { 
     dbc.Open(); 

     Task<int> task1 = MergeOneDataTableAsync(); 
     Task<int> task2 = MergeTwoDataTableAsync(); 

     int[] results = await Task.WhenAll(new Task<int>[]{task1,task2}); 

     recordedStatistics.Add("mergeOne", results[0]); 
     recordedStatistics.Add("mergeTwo", results[1]); 
    } 

    //... 
    return recordedStatistics; 
} 

Ma questo vorrebbe dire che si deve richiamare in modo asincrono (async all the way).

+0

Sembra fantastico. Si tratta, in effetti, di un'applicazione GUI, ma mi aspetto che blocchi la GUI fino al completamento delle query. Una volta terminato, caricherò una vista con il numero di record interessati da ciascuna query. Voglio/ho bisogno di quell'informazione, quindi o faccio da baby-sitter alla pagina chiamante, o torno indietro una volta che le ho dato un minuto per completarla. Non ho visto il punto nell'attesa di 5 minuti per query, uno dopo l'altro, quando posso aspettare gli stessi 5 minuti per completare tutti in una volta. Grazie! – FlipperBizkut

+4

In realtà si è verificato un deadlock in attesa di chiamata 'Task.WaitAll' sulle attività create da' MergeOneDataTableAsync' e 'MergeTwoDataTableAsync', che utilizzano entrambi' await' senza 'ConfigureAwait (false)'. –

+0

Potresti approfondire il problema del deadlock? Forse mostrare un modo per evitarlo pure? – FlipperBizkut

Problemi correlati