Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async Await Few Confusions

As experiencing the new async & Await features of 4.5 I want to clear some confusions before going any further. I have been reading different article and also different question on SO and it help me undertands how Async and Await works. I will just try to put my understanding and confusions here and will appreciate if someone code educate me and other people who are looking for same things. I am discussing this in very simple wordings.

So Async is used so that compiler know that method marked by Async contains Await operation (Long operation). Latest framework contains different new builtin methods for Async Operations.

The builtin Async functions like connection.OpenAsync, ExecuteScalarAsync etc are used with Await keyword. I don't know the inner working of these Async Methods but my strong guess is that under the hood they are using Tasks.

Can I make this as general rule that Await will be with any method which implements Task. So if I need to create my own method which is performing long operation then will I create it as Task and when it is called I will use Await Keyword with it?

Second most important thing is that what is the rule of thumb of creating a method as Async or creating it as task. For example,

public void SampleMain()
{
    for (int i = 1; i <= 100; i++)
    {
         DataTable dt = ReadData(int id);
    }
}

public DataTable ReadData(int id)
{ 
     DataTable resultDT = new DataTable();

     DataTable dt1 = new DataTable();
     // Do Operation to Fill DataTable from first connection string
     adapter.Fill(dt1);

     DataTable dt2 = new DataTable();
     // Do Operation to Fill DataTable from first connection string
     adapter.Fill(dt2); 

     // Code for combining datatable and returning the resulting datatable
     // Combine DataTables
     return resultDT;
}

public string GetPrimaryConnectionString()
{
     // Retrieve connection string from some file io operations
     return "some primary connection string";
}

public string GetSecondaryConnectionString()
{
     // Retrieve connection string from some file io operations
     return "some secondaryconnection string";
}

Now this is a very simple scenario that I have created based on some real world application I worked in past. So I was just wondering how to make this whole process Async.

Should I make GetPrimaryConnectionString and GetSecondaryConnectionString as Tasks and Await them in ReadData. Will ReadData be also a Task? How to call ReadData in the SampleMain function?

Another way could be to create a Task for ReadData in SampleMain and run that Task and skip converting other methods as Task. Is this the good approach? Will it be truly Asynchronous?

like image 313
Adnan Yaseen Avatar asked Aug 13 '15 07:08

Adnan Yaseen


People also ask

Can one async function have multiple awaits?

In order to run multiple async/await calls in parallel, all we need to do is add the calls to an array, and then pass that array as an argument to Promise. all() . Promise. all() will wait for all the provided async calls to be resolved before it carries on(see Conclusion for caveat).

What happens if we dont use async await?

The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete. In most cases, that behavior isn't expected.

Why async await over simole promise chains?

Async/Await is used to work with promises in asynchronous functions. It is basically syntactic sugar for promises. It is just a wrapper to restyle code and make promises easier to read and use. It makes asynchronous code look more like synchronous/procedural code, which is easier to understand.

Is async await slower than promises?

I found out that running async-await can be much slower in some scenarios. But if I click on the 'both' button, the 'await' version is ~3-4 times slower than the promises version.


3 Answers

So Async is used so that compiler know that method marked by Async contains Await operation

The async is used so that the compiler will have an indication to create a state-machine out of the method. An async method can have no await, and still work, though it will execute completely synchronously.

The builtin Async functions like connection.OpenAsync, ExecuteScalarAsync etc are used with Await keyword. I don't know the inner working of these Async Methods but my strong guess is that under the hood they are using Tasks.

Task is a promise of work to be completed in the future. There are a couple of ways to create a Task. But, Task isn't the only thing that can be represent an asynchronous operation. You can create an awaitable yourself if you wanted, all it needs it to implement a GetAwaiter method which returns a type implementing INotifyCompletion.

If you want to know how a method is implemented in the framework, you can view the source. In this particular case, they use TaskCompletionSource<T>.

Should I make GetPrimaryConnectionString and GetSecondaryConnectionString as Tasks and Await them in ReadData. Will ReadData be also a Task? How to call ReadData in the SampleMain function?

There is nothing inherently asynchronous about retrieving a connection string. You usually (not always) use async-await with naturally async IO operations. In this particular case, the only actual async operation is ReadData, and if you want to make it asynchronous, you can use SqlDataReader, which exposes async methods.

An example, taken from the ADO.NET teams blog:

public static async Task<Product> GetProductAndReviewsAsync(
            int productID, int reviewsToGet)

{
    using (SqlConnection connection = new SqlConnection(ConnectionString))
    {
        await connection.OpenAsync();
        const string commandString = GetProductByIdCommand + ";" 
                                    + GetProductReviewsPagedById;

        using (SqlCommand command = new SqlCommand(commandString, connection))
        {
            command.Parameters.AddWithValue("productid", productID);
            command.Parameters.AddWithValue("reviewStart", 0); 
            command.Parameters.AddWithValue("reviewCount", reviewsToGet);
            using (SqlDataReader reader = await command.ExecuteReaderAsync())
            {
                if (await reader.ReadAsync())
                {
                    Product product = GetProductFromReader(reader, productID);
                    if (await reader.NextResultAsync())
                    {
                        List<Review> allReviews = new List<Review>();
                        while (await reader.ReadAsync())

                        {
                            Review review = GetReviewFromReader(reader);
                            allReviews.Add(review);
                        }
                        product.Reviews = allReviews.AsReadOnly();
                        return product;
                    }
                    else
                    {
                        throw new InvalidOperationException(
                            "Query to server failed to return list of reviews");
                    }
                }
                else
                {
                    return null;
                }
            }
        }
    }
}
like image 61
Yuval Itzchakov Avatar answered Oct 14 '22 01:10

Yuval Itzchakov


how to make this whole process Async

If there are asynchronous versions of adapter.Fill, then simply await for it in ReadData, which in turn also become async and you can await for it in the caller method:

// in async button click event
button.Enabled = false;
var dt = await ReadData(int id);
button.Enabled = true;
... // do something with dt

public async Task<DataTable> ReadData(int id)
{ 
     ...
     var job1 = adapter.AsyncFill(dt1);
     var job2 = adapter.Fill(dt2); 
     // wait for all of them to finish
     Task.WaitAll(new[] {job1, job2});
     ...
     return Task.FromResult(resultDT); // dump approach
}

If there are no asynchronous version then you have to create them (by using Task):

// in async button click event
button.Enabled = false;
// run synchronous task asynchronously
var dt = await Task.Run(() => ReadData(int id));
button.Enabled = true;
... // do something with dt

async/await shines when it comes to UI, otherwise (if no UI is involved) just create task and run synchronous operation there.

like image 1
Sinatr Avatar answered Oct 14 '22 01:10

Sinatr


The only reason to use async-await is if your main thread might do something useful while another thread is doing the length operation. If the main thread would start the other thread and only wait for the other thread to finish, it is better to let the main thread do the action.

One of the things a main thread quite often does is keep the UI responsive.

You are right, under the hood async-await uses Task, hence you see that an async function returns a Task.

The rules:

  • If a function would return void, the async version returns Task. If the function would return TResult, the async version should return Task<TResult>.
  • There is one exception: the async event handler returns void.
  • The return value of await Task is void. The return value of await Task<TResult> is TResult.
  • Only async functions can call other async functions.
  • If you have a non-async function you can still use the async function. However you cannot use await. Use the Task return value of the async function and the System.Threading.Tasks.Task methods to wait for the results.
  • If you have an async function and want to start a non-async function in a separate thread, use:

    private int SlowCalculation(int a, int b) { System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5)); return a + b; }

    private async Task CalculateAsync(int a, int b) { Task myTask = Task.Run( () => SlowCalculation(a, b); // while SlowCalcuation is calculating slowly, do other useful things // after a while you need the answer int sum = await myTask; return sum; }

See that the return of await Task<int> is int.

Some people used to use functions like Task.ContinueWith. Because of the await statement that is not needed anymore. Await makes sure that the task is finished. The statement after the await is what you'd normally do in the ContinueWith.

In Task.ContinueWith you could say: "do this only if the task failed". The async-await equivalent for this is try-catch.

Remember: if your thread has nothing useful to do (like keeping your UI responsive), don't use async-await

Starting several tasks in async-await and waiting for them to finish is done as follows:

private async Task MyAsyncFunction(...)
{
    var tasks = new List<Task<int>>();
    for (int i=0; i<10; ++i)
    {
        tasks.Add(CalculateAsync(i, 2*i);
    }
    // while all ten tasks are slowly calculating do something useful
    // after a while you need the answer, await for all tasks to complete:
    await Task.WhenAll(tasks);
    // the result is in Task.Result:
    if (task[3].Result < 5) {...}
}

The async-await version of Task.Waitall is Task.WhenAll. WhenAll returns a Task instead of void, so you can await for it. The main thread remains responsive even while awaiting.

The main thread is not the case when using Task.WaitAll, because you don't await.

like image 1
Harald Coppoolse Avatar answered Oct 14 '22 00:10

Harald Coppoolse