2016-02-13 10 views
5

Я ищу подходящий способ обработки нескольких запросов к базе данных, которые, вероятно, выиграют от одновременного запуска. Запросы - это просто хранимые процедуры, которые либо делают вставки, либо слияния, используя данные, которые программно собраны в DataTables в моем приложении ASP.NET MVC.Как правильно выполнять асинхронные/параллельные вызовы базы данных

Конечно, я видел некоторую информацию о async и await, и это похоже на то, что мне нужно будет сделать, но у меня нет четкого понимания того, как его реализовать. Некоторая информация говорит о том, что вызовы будут по-прежнему последовательными, и что они все еще будут ждать по другому. Это кажется бессмысленным.

В конечном счете, я хотел бы получить решение, позволяющее запускать все запросы за время, необходимое для выполнения самой длинной процедуры. Я хотел бы, чтобы все запросы возвращали количество затронутых записей (как и сейчас).

Вот что я собираюсь сейчас (это ни в коей мере параллельно):

// 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; 

Все, что код находится в пределах того же метода, который собирает данные для DataTables. Мое ограниченное понимание async заставило бы меня поверить, что мне нужно будет извлечь предыдущий код в свой собственный метод. Тогда я бы назвал этот метод и возвращал await. Тем не менее, я даже не знаю достаточно об этом, чтобы начать.

Я раньше не делал асинхронного/параллельного/многопоточного кодирования. Эта ситуация только заставляет меня чувствовать, что это идеальное время для перехода. Тем не менее, я хотел бы узнать лучший способ, вместо того, чтобы отучить неправильный путь.

ответ

7

Вот пример того, как вы могли бы сделать это:

Здесь я создаю два метода, чтобы обернуть две операции, вы должны сделать то же самое для других операций:

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

Обратите внимание, что Я использую метод ExecuteNonQueryAsync для выполнения запроса.

И тогда ваш оригинальный метод будет выглядеть следующим образом:

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

Пожалуйста, обратите внимание, что я держу этот метод синхронно. Другой вариант (на самом деле лучше один), чтобы преобразовать метод в асинхронном один, как это:

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; 
} 

Но это означало бы, что вы должны вызвать его асинхронно (async all the way).

+0

Это выглядит великолепно. На самом деле это приложение GUI, но я ожидаю, что он закроет GUI до тех пор, пока запросы не будут завершены. Как только они будут выполнены, я загружу представление с количеством затронутых записей из каждого из запросов. Я хочу/нуждаюсь в этой информации, поэтому я либо сижу на вызывающей странице, либо возвращаюсь к ней, как только я дам ей минутку. Я просто не видел смысла в ожидании 5 минут за запрос один за другим, когда я могу подождать те же 5 минут, чтобы все они заполнились сразу. Благодаря! – FlipperBizkut

+4

У вас действительно есть тупик, ожидающий вызова «Task.WaitAll» в задачах, созданных «MergeOneDataTableAsync» и «MergeTwoDataTableAsync», которые оба используют 'await' без' ConfigureAwait (false) '. –

+0

Не могли бы вы рассказать о проблеме взаимоблокировки? Возможно, есть способ избежать этого? – FlipperBizkut

Смежные вопросы