Asynchronous Programming in C#

 

In this post we'll see what asynchronous programming is, and most importantly, when and where to use it.

 

1 - What is asynchronous programming?

As the name suggests, it allows us to write code that executes in a parallel manner.

It’s very important not to confuse it with TPL (Task parallelization), as although they are similar concepts, they are not the same.

A very common mistake when starting to code is to think that more Task means multithreading, or that each Task will be a new thread, which does not always have to be the case. When we invoke Task we are calling the Task parallel library, which will handle all the thread management for us.

The great advantage of using async / await is that it allows us to write parallel code in a very simple and easy way.

I'll try to write this post with a real-world example, as I feel like that will make things clearer.

 

2 - How to use asynchronous programming in C# with Async / await

The first thing is to see how we write code to make it asynchronous. The code is available on GitHub and I’ll use the same example as in the post on how to connect to a database.

 We have the following code that we’ve decided to make asynchronous:

public Articulo GetArticulo(int id)
{
    using(MySqlConnection conexion = new MySqlConnection(conexionString))
    {
        //Codigo para el select
        conexion.Open();
        MySqlCommand comando = new MySqlCommand();
        comando.CommandText = "select * from articulo where id = ?articuloId;";
        comando.Parameters.Add("?articuloId", MySqlDbType.Int32).Value = id;
        comando.Connection = conexion;

        Articulo articulo = new Articulo();
        using (var reader = comando.ExecuteReader())
        {
            while (reader.Read())
            {
                articulo.Id = Convert.ToInt32(reader["Id"]);
                articulo.Titulo = reader["Titulo"].ToString();
                articulo.Contenido = reader["Contenido"].ToString();
            }

            return articulo;
        }
    }
}

As we can see, this code queries the database. The way to convert this method to asynchronous is very easy; we just need to indicate that instead of returning an Articulo class, it returns Task<T> where, as we saw in generics, T is Articulo.

public Task<Articulo> GetArticulo(int id){...}

You will see several errors pop up, the first being that we need to use the System.Threading.Tasks library, and the second is that we’re not actually using tasks inside our method.

If we've marked the method as Task<T>, it means we want it to run asynchronously. In this situation, we can call the database asynchronously. Let’s see how.

The first step is opening the connection, which we can do asynchronously. Currently, we do conexion.Open();. A common convention when writing libraries is that if a method is asynchronous, we append async to its name, so we can open the database connection with conexion.OpenAsync();. But this method DOES NOT actually open the connection right away; we must wait for it to open. For that, we use the await keyword. So it looks like this:

await conexion.OpenAsync();

When we use await, the method’s header must include the async keyword, so the method will look like this:

public async Task<Articulo> GetArticulo(int id){...}

And now we have our asynchronous method:

public async Task<Articulo> GetArticulo(int id)
{
    using(MySqlConnection conexion = new MySqlConnection(conexionString))
    {
        //Codigo para el select
        await conexion.OpenAsync();
        MySqlCommand comando = new MySqlCommand();
        comando.CommandText = "select * from articulo where id = ?articuloId;";
        comando.Parameters.Add("?articuloId", MySqlDbType.Int32).Value = id;
        comando.Connection = conexion;

        Articulo articulo = new Articulo();
        using (var reader = await comando.ExecuteReaderAsync())
        {
            while (reader.Read())
            {
                articulo.Id = Convert.ToInt32(reader["Id"]);
                articulo.Titulo = reader["Titulo"].ToString();
                articulo.Contenido = reader["Contenido"].ToString();
            }

            return articulo;
        }
    }
}

But making the method asynchronous is not enough; when we use Task<> somewhere in the process, we need to run the whole process asynchronously. That means all the methods in the process should use Task<T>.

For example, the method that calls our recently updated method. We’ll convert it from:

public Articulo ConsultarArticulo(int id)
{
    return _articuloRepository.GetArticulo(id);
}

to:

public async Task<Articulo> ConsultarArticulo(int id)
{
    return await _articuloRepository.GetArticulo(id);
}

And like this, all the methods used by our asynchronous process.

 

2.1 - Await in C#

As I just mentioned, to get the object we want from a method that returns Task<T>, we need to wait for it, and for that, we use the await keyword.

But we must be careful, as we can easily turn our asynchronous code into synchronous code. For example, the following code:

Articulo articulo1 = await _articuloRepository.GetArticulo(1);
Articulo articulo2 = await _articuloRepository.GetArticulo(2);
Articulo articulo3 = await _articuloRepository.GetArticulo(3);

Executes the statements one at a time, in order; it waits for the first with id 1 to finish, then starts the second, and so on.

To execute these actions asynchronously, we need to call the method that returns a Task, store that Task in a variable, and await them afterward:

Task<Articulo> taskArticulo1 =  _articuloRepository.GetArticulo(1);
Task<Articulo> taskArticulo2 =  _articuloRepository.GetArticulo(2);
Task<Articulo> taskArticulo3 =  _articuloRepository.GetArticulo(3);

Articulo articulo3 = await taskArticulo3;
Articulo articulo2 = await taskArticulo2;
Articulo articulo1 = await taskArticulo1;

2.2 - Task.WhenAll Method in C#

Alternatively, when we have multiple tasks to execute, C# provides us with a method called Task.WhenAll(IEnumerable<Task>) that lets us specify a list of Task to run.

Task<Articulo> taskArticulo1 = _articuloRepository.GetArticulo(1);
Task<Articulo> taskArticulo2 = _articuloRepository.GetArticulo(2);
Task<Autor> taskAutor1 = _autorRepository.GetAutor(1);

await Task.WhenAll(taskArticulo1, taskArticulo2, taskAutor1);

Articulo articulo1 = taskArticulo1.Result;
Articulo articulo2 = taskArticulo2.Result;
Autor autor1 = taskAutor1.Result;

As we can see, we can include any type of Task in our list, and later access the result via the .Result property.

Note: If we try to access the .Result property before awaiting with await, we can't, as the task won’t have completed yet.

 

We should prioritize using Task.WhenAll over the multiple-await pattern for several reasons:

  • The code looks cleaner.
  • It propagates errors correctly. If you have 10 tasks with await and one of the first fails, you could lose the error.
  • Using WhenAll waits until ALL the tasks finish, even if there are errors. If you handle things manually with try{}catch() and one fails, part of your code may want to throw an exception while another part is still waiting , this can lead to bugs or hangs.

 

3 - When to use asynchronous programming in C#

We should use asynchronous programming whenever possible, plain and simple. The benefits it brings , especially in performance and responsiveness , are enormous.

Here’s a clear example: imagine calling 3 APIs from your program, one after another synchronously. The timing chart looks like this:

synchronous calls

 

As we can see, the calls to all 3 external services take a total of 15.5 seconds.

 

Whereas if we make these calls asynchronously, it takes only 6 seconds.

asynchronous calls

This logic applies to any service. For example, if you're querying several tables from the database, you can make them all asynchronously.

 

Conclusion

Making all calls asynchronously makes our code much faster, because when making multiple calls to external services, for example, we don’t have to wait one by one for each to return. Instead, we wait until all are done and ready for us.

Using async / await makes our code much clearer and cleaner than using threads, which, honestly, is a mess.

 

This post was translated from Spanish. You can see the original one here.
If there is any problem you can add a comment bellow or contact me in the website's contact form

Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2025 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café