web-dev-qa-db-fra.com

Comment effectuer correctement des appels de base de données asynchrones / parallèles

Je cherche la bonne façon de gérer plusieurs appels de base de données qui bénéficieraient probablement d'une exécution simultanée. Les requêtes concernent uniquement les procédures stockées qui effectuent des insertions ou des fusions à l'aide de données qui sont assemblées par programme dans DataTables dans mon application ASP.NET MVC.

Bien sûr, j'ai vu des informations sur async et await, et cela semble être ce que je devrais faire, mais je n'ai pas une compréhension claire de la façon de l'implémenter. Certaines informations indiquent que les appels seraient toujours séquentiels et que l'un attendrait toujours qu'un autre soit terminé. Cela semble inutile.

En fin de compte, je voudrais une solution qui me permette d'exécuter toutes les requêtes dans le temps qu'il faut pour que la procédure la plus longue se termine. J'aimerais que toutes les requêtes renvoient également le nombre d'enregistrements concernés (comme elles le font maintenant).

Voici ce que je fais maintenant (qui n'est en aucun cas parallèle):

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

Tout ce code se trouve dans la même méthode qui assemble les données pour les DataTables. Ma compréhension limitée de async me conduirait à croire que j'aurais besoin d'extraire le code précédent dans sa propre méthode. J'appellerais alors cette méthode et await le retour. Cependant, je n'en sais même pas assez pour commencer.

Je n'ai jamais fait de codage asynchrone/parallèle/multithread auparavant. Cette situation me donne juste l'impression que c'est le moment idéal pour me lancer. Cela dit, j'aimerais apprendre la meilleure façon, au lieu d'avoir à désapprendre de la mauvaise façon.

18
FlipperBizkut

Voici un exemple de la façon dont vous le feriez:

Ici, je crée deux méthodes pour encapsuler deux opérations, vous devez faire de même pour les autres opérations:

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

Notez que j'utilise la méthode ExecuteNonQueryAsync pour exécuter la requête.

Et puis votre méthode d'origine ressemblerait à ceci:

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

Veuillez noter que je garde cette méthode synchrone. Une autre option (en fait une meilleure) consiste à convertir la méthode en une méthode asynchrone comme celle-ci:

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

Mais cela signifierait que vous devez l'invoquer de manière asynchrone ( async complètement ).

17
Yacoub Massad