Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Too many nested async methods. Is that a problem?

The following code gets a list of investments belonging to a customer from 3 different resources. The flow starts with a controller's call and follows the flow described below where all methods are declared as async and called with await operator.

I'm wondering if is there a problem making all methods as async. Is there any performance penalty? Is it a code smell or an anti-pattern?

I know there are things that must be waited like access url, get data from cahce, etc. But I think there are things like filling a list or sum some few values doesn't need to be async.

enter image description here

Below follow the code (some parts where ommited for clearness):

Controller

    {HttpGet]
    public async Task<IActionResult> Get()
    {
        Client client = await _mediator.Send(new RecuperarInvestimentosQuery());
        return Ok(cliente);
    }

QueryHandler

    public async Task<Client> Handle(RecoverInvestimentsQuery request, CancellationToken cancellationToken)
    {
        Client client;
        List<Investiment> list = await _investimentBuilder.GetInvestiments();
        client = new Cliente(request.Id, list);
        return client;
    }

InvestmentBuilder

    public async Task<List<Investiment>> GetInvestiments()
    {
        ListInvestiments builder = new ListInvestiments();
        await builder.BuildLists(_builder);
        // here I get the List<Investiment> list already fulfilled to return to the controller
        return list;
    }

BuildLists

    public async Task BuildLists(IBuilder builder)
    {
        Task[] tasks = new Task[] {
            builder.GetFundsAsync(),   //****
            builder.ObterTesouro(),
            builder.ObterRendaFixa()
        };
        await Task.WhenAll(tasks);
    }

Funds, Bonds and Fixed Income Services (***all 3 methods are equal, only its name vary, so I just put one of them for the sake of saving space)

    public async Task GetFundsAsync()
    {
        var listOfFunds = await _FundsService.RecoverFundsAsync();
        // listOfFunds will get all items from all types of investments
    }

Recover Funds, Bonds and Fixed Incomes methods are equals too, again I just put one of them

    public async Task<List<Funds>> RecoverFundsAsync()
    {
        var returnCache = await _clientCache.GetValueAsync("fundsService");
        // if not in cache, so go get from url
        if (returnCache == null)
        {
            string url = _configuration.GetValue<string>("Urls:Funds");
            var response = await _clienteHttp.ObterDadosAsync(url);
            if (response != null)
            {
                string funds = JObject.Parse(response).SelectToken("funds").ToString();
                await _clienteCache.SetValueAsync("fundService", funds);
                return JsonConvert.DeserializeObject<List<Funds>>(fundos);
            }
            else
                return null;
        }
        return JsonConvert.DeserializeObject<List<Funds>>(returnCache);
    }

HTTP Client

    public async Task<string> GetDataAsync(string Url)
    {
        using (HttpClient client = _clientFactory.CreateClient())
        {
            var response = await client.GetAsync(Url);
            if (response.IsSuccessStatusCode)
                return await response.Content.ReadAsStringAsync();
            else
                return null;
        }
    }

Cache Client

    public async Task<string> GetValueAsync(string key)
    {
        IDatabase cache = Connection.GetDatabase();
        RedisValue value = await cache.StringGetAsync(key);
        if (value.HasValue)
            return value.ToString();
        else
            return null;
    }

Could someone give a thought about that?

Thanks in advance.

like image 785
Valmir Cinquini Avatar asked Oct 18 '25 14:10

Valmir Cinquini


1 Answers

Your code looks okay for me. You are using async and await just for I/O and web access operations, and it perfectly fits for async and await purposes:

  • For I/O-bound code, you await an operation that returns a Task or Task inside of an async method.
  • For CPU-bound code, you await an operation that is started on a background thread with the Task.Run method.

Once you've used async and await, then all pieces of your code tends to become asynchronous too. This fact is described greatly in the MSDN article - Async/Await - Best Practices in Asynchronous Programming:

Asynchronous code reminds me of the story of a fellow who mentioned that the world was suspended in space and was immediately challenged by an elderly lady claiming that the world rested on the back of a giant turtle. When the man enquired what the turtle was standing on, the lady replied, “You’re very clever, young man, but it’s turtles all the way down!” As you convert synchronous code to asynchronous code, you’ll find that it works best if asynchronous code calls and is called by other asynchronous code—all the way down (or “up,” if you prefer). Others have also noticed the spreading behavior of asynchronous programming and have called it “contagious” or compared it to a zombie virus. Whether turtles or zombies, it’s definitely true that asynchronous code tends to drive surrounding code to also be asynchronous. This behavior is inherent in all types of asynchronous programming, not just the new async/await keywords.

like image 188
StepUp Avatar answered Oct 21 '25 02:10

StepUp