简体   繁体   中英

Individual await vs Task.WhenAll

I have the following two methods, who produce the same results.

public static async Task<IEnumerable<RiskDetails>> ExecuteSqlStoredProcedureSelect<T>(IEnumerable<AccountInfo> linkedAccounts, string connectionString, string storedProcedure, int connTimeout = 10)
{
        var responseList = new List<RiskDetails>();

        using (IDbConnection conn = new SqlConnection(connectionString))
        {
            foreach (var account in linkedAccounts)
            {
                var enumResults = await conn.QueryAsync<RiskDetails>(storedProcedure, 
                    new { UserID = account.UserID, CasinoID = account.CasinoID, GamingServerID = account.GamingServerID, AccountNo = account.AccountNumber, Group = account.GroupCode, EmailAddress = account.USEMAIL }, 
                    commandType: CommandType.StoredProcedure);
                    
                if (enumResults != null)
                        foreach (var response in enumResults)
                            responseList.Add(response);
            }
         }

         return responseList;
    }
        
    public static async Task<IEnumerable<RiskDetails>> ExecuteSqlStoredProcedureSelectParallel<T>(IEnumerable<AccountInfo> linkedAccounts, string connectionString, string storedProcedure, int connTimeout = 10)
    {
        List<Task<IEnumerable<RiskDetails>>> tasks = new List<Task<IEnumerable<RiskDetails>>>();
        var responseList = new List<RiskDetails>();

        using (IDbConnection conn = new SqlConnection(connectionString))
        {
            conn.Open();

            foreach (var account in linkedAccounts)
            {
                var enumResults = conn.QueryAsync<RiskDetails>(storedProcedure,
                        new { UserID = account.UserID, CasinoID = account.CasinoID, GamingServerID = account.GamingServerID, AccountNo = account.AccountNumber, Group = account.GroupCode, EmailAddress = account.USEMAIL },
                        commandType: CommandType.StoredProcedure, commandTimeout: 0);

                //add task
                tasks.Add(enumResults);
            }

            //await and get results
            var results = await Task.WhenAll(tasks);
            foreach (var value in results)
                foreach (var riskDetail in value)
                    responseList.Add(riskDetail);
        }

        return responseList;
    }

My understanding of how ExecuteSqlStoredProcedureSelect executes, is as follow:

  • Execute Query for Account #1
  • Wait for result of query #1
  • Receive Result for query #1
  • Execute Query for Account #2
  • Wait for result of query #2
  • etc.

My understanding of how ExecuteSqlStoredProcedureSelectParallel executes, is as follow:

  • Add all tasks to an IEnumerable instance
  • Call Task.WhenAll , which will start executing queries for Account #n
  • Queries are executed relatively parallel against SQL server
  • Task.WhenAll returns when all queries executed

From my understanding, ExecuteSqlStoredProcedureSelectParallel there should be a little improvement with this function with regards to time, but at the moment there is none.

Is my understanding for this wrong?

Your understanding of ExecuteSqlStoredProcedureSelectParalel is not completely correct.

Call Task.WhenAll, which will start executing queries for Account #n

Task.WhenAll does not start anything. After QueryAsync method returns - the task has already started and is running or even completed. When control reaches Task.WhenAll - all tasks are have already started.

Queries are executed relatively parallel against SQL server

This is complicated subject. To be able to execute multiple queries over the same sql connection concurrently - you have MultipleActiveResultSets option be enabled in your connection string, will not work (throw exception) without that.

Then, in many places, including documentation , you can read that MARS is not about parallel execution. It's about statement interleaving, which means SQL Server might switch between different statements executing over the same connection, much like OS might switch between threads (on the single core). Quote from the above link:

MARS operations execute synchronously on the server . Statement interleaving of SELECT and BULK INSERT statements is allowed. However, data manipulation language (DML) and data definition language (DDL) statements execute atomically. Any statements attempting to execute while an atomic batch is executing are blocked. Parallel execution at the server is not a MARS feature .

Now, even if your select queries execute in parallel on server, that won't help you much in terms of "perfomance", if those queries execution is fast.

Suppose you query for 10 accounts, and each query execution takes 1ms (pretty normal, I'd say expected situation). But, each query returns say 100 rows. Now, those 100 rows should be delivered over the network to the caller. That's the most costly part, execution time is negligible compared to that (in this specific example). Whether you use MARS or not - you have just one physical connection with sql server. Even if your 10 queries are executed in parallel on server (which I doubt because of the above) - their results cannot be delivered to you in parallel, because you have one physical connection. So 10*100 = 1000 rows, in both cases, are delivered to you "sequentially".

From that it should be clear that you should not expect your Parallel version to execute noticably faster. If you want it to be really parallel - use separate connection for each command.

I want to also add that number of physical cores on your machine has NO non-negligible impact on perfomance in this situation. Asynchronous IO is not about blocking threads, and you might read in numerous places over internet.

Well, you understanding is correct but you need to understand underlying cores , number of physical cores you machine have.

You can create multiple task at a given time , but that doesnt mean that all that task run in parellel, each task represent thread and the get scheduled on physcial core, in turn one core run one thread at time.

So if you machine is having let 4 core and you created 8 thread then you mahcine will run 4 thread only, other thread will get turn when thread scheduled on core in case running thread blocked or in wait state or completed.

By above I means to say when you do parallel code you should also consider number of physical core you are having on your machine. that could be one reason you code is not getting advantage of parallel coding you have done.

One more thing if number of cores are less then number of task/thread then there will be too much context switching when can slow down you program also.

Adding to above , Task parallel library under the hood make use of Threadpool, and threads in thread pool recommended to use for small operation. because long running operation may consume you thread pool and then your short running operation has to wait for thread to finish , which also slow down your application. So its recommended to create task with TaskCreationOptions.LongRunning or make use of async/await so you threadpool thread not get cosume for long running operations (database operation, file read/write operation or external web/ webservcie call to get data).


Apart from above in your code,

 var results = await Task.WhenAll(tasks);

this means wait till all task execution get completed, which means that if you have 5 task and 3 of them completed but 2 of them is taking longer time to completed , then you code will wait for that 2 long running task to complete before executing next line.


Check this also : can a single SQL Server connection be shared among tasks executed in parallel

A SQLServer Connection can be shared by multiple tasks executing in parallel, eg threads in a C# program or requests in an app server. But most use scenarios would require you to synchronize access to the Connection. A task will have to wait for the connection if another task is using it. By the time you build a shared connection mechanism that does not break or become a performance constraint for your parallel tasks, you have likely built a connection pool.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM