2

I have an app to move data from a MS SQL server into a MySQL server using async query execution; the data moves fine but Task.WaitAll(tasks) call in the RunAllTasks() method never completes.

The async tasks all follow the pattern of PumpLocsAsync() where the call to MS SQL is invoked asynchronously via BeginExecuteReader; when the reader returns with results, then MySQL inserts normally.

..

async Task PumpLocsAsync()
{
     using (var conn = new SqlConnection(SqlConnStr))
     using (var cn = new MySqlConnection(MysqlConnStr))
     using (var cmd = new SqlCommand())
     {
          cmd.Connection = conn;
          cmd.CommandText = "SELECT HERE";

          conn.Open();
          var handle = cmd.BeginExecuteReader();
          await Task.Factory.FromAsync(handle, (ar) =>
          {
               var rdr = cmd.EndExecuteReader(ar);
               var qry = @"INSERT HERE";

               cn.Open();
               using (var cmdm = new MySqlCommand(qry, cn))
               {
                   cmdm.CommandTimeout = MysqlCmdtimeout;
                   <PARAM SETUP HERE>
                   <COLUMN MAPPING HERE>

                   while (RetryUtility.RetryMethod<bool>(() => rdr.Read(), 3, 1000))
                   {
                       <LOADING PARAMS WITH BITS HERE>
                       RetryUtility.RetryAction(() => cmdm.ExecuteNonQuery(), 3, 1000);
                   }
               }
               Console.WriteLine("Finished Locs!");
               cn.Close();
          });
          conn.Close();
    }
}


...

        void RunAllTasks()
        {
            Task[] tasks = { PumpLocsAsync(), PumpPicsAsync() };

            try
            {
                Task.WaitAll(tasks);
                Console.WriteLine("Finished with all tasks...");
                foreach (var task in tasks)
                {
                    Console.WriteLine("Id: {0}, Status: {1}", task.Id, task.Status);
                }
            }

....

1 Answer 1

7

You misunderstood how to call Task.Factory.FromAsync(), you pass to it the result of the BeginXxx method and a delegate to the EndXxx method:

var rdr = await Task.Factory.FromAsync(
    cmd.BeginExecuteReader,
    (Func<IAsyncResult, SqlDataReader>)cmd.EndExecuteReader,
    null);

If you want to do something after the operation completes, simply put it below this line, await will make sure it executes at the right time.

Sign up to request clarification or add additional context in comments.

9 Comments

Do you mean something like this? var rdr = await Task.Factory.FromAsync(cmd.BeginExecuteReader() , (Func<IAsyncResult, SqlDataReader>)cmd.EndExecuteReader);
Whenever possible, I recommend passing a delegate to the Begin* method instead of the result of the Begin* method. FromAsync is more efficient if it can wrap the entire Begin/End transaction into a Task. If it only has an IAsyncResult, then it has to call ThreadPool.RegisterWaitForSingleObject (and you can only have 64 of those in an app domain) on the AsyncWaitHandle (which is usually lazy-created). OTOH, if it has both delegates, then it can provide its own AsyncCallback to the Begin* method, and the TaskCompletionSource wrapper is quite simple.
Followup: Stephen Toub has a blog post on why the delegate version is preferred.
I must be missing something because while the code above compiles, execution never gets past it. Shouldn't I just be able to use the rdr on the next line or does the 2nd param return need more wiring? Thanks
Yeah, you should be able to use rdr on the line, as if it was synchronous code. I have no idea where your problem could be.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.