Как правильно выполнять асинхронные / параллельные вызовы базы данных

Я ищу правильный способ обработки нескольких вызовов базы данных, которые, вероятно, выиграют от одновременного запуска. Запросы относятся только к хранимым процедурам, которые выполняют вставки или слияния, используя данные, которые программно собираются в 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 Возврат. Тем не менее, я даже не знаю достаточно об этом, чтобы начать.

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

1 ответ

Решение

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

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

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

Но это будет означать, что вы должны вызывать его асинхронно (полностью асинхронно).

Другие вопросы по тегам