简体   繁体   中英

How to use async/await in scenario with multiple tasks with each task requiring a new async/await

I have the following scenario:

Customer has a number of accounts, each of them has a number of cards attached.

Now I have a request where I need to query accounts with cards on multiple customers. I have async methods to query accounts and cards separately, ie FindAccounts(string customer) , FindCards(string[] accounts) .

So I have this method:

public async Task<Data> FindCustomersWithCards(string[] customers)
{
     var accountsTasks = customers.Select(_service.FindAccounts);
     var accounts = await Task.WhenAll(accountsTasks);

     var cardsTasks = accounts.Select(_service.FindCards);
     var cards = await Tasks.WhenAll(cardsTasks)        
     ...
}

While this will work it has a problem that you have to wait for accounts of all customers to be finished before cards can be queried. A more efficient implementation would go on and query cards for customer accounts as soon as accounts querying for a particular customer is finished (without waiting for other customers).

My question is if it is possible to do this with async/await . I think I can manage with ContinueWith , but I am not 100% sure it is OK to mix async/await with ContinueWith approach.

It might be more sensible to split it by customer and async within that:

private async Task<Card> FindCardForCustomerAsync(string customer)
{
  var account = await _service.FindAccountAsync(customer);
  return await _service.FindCardAsync(account);
}

public async Task<Data> FindCustomersWithCards(string[] customers)
{
  var cardsTasks = customers.Select(FindCardForCustomerAsync);
  var cards = await Tasks.WhenAll(cardsTasks)
  …
}

However, it's worth considering the balance of efficiencies of how your FindAccounts and FindCards work. Eg if they work as a single SELECT pushed to a database then the greater concurrency of turning it into multiple smaller bits of work may not be worth the greater amount of overhead that has. It can be the case that waiting for 20 or even 200 results is only marginally slower than waiting for 1, and then splitting into 20 requests gains very little, even before the extra connections involved are considered.

It is hard to say if the tasks per customer approach will bring you the benefit, the best way to find it out is to make a test on your scenario.

I made a simple (event that it may look quite messy) example how the first approach can be modified to allow execution of tasks per each customer.
You have mentioned that you can manage this, I just wanted to post it here so anyone interested in that can play with it.

I used Task.Run(...) to simulate async tasks.

public class Account
{
    public string AccountName { get; set; }
    public string CustomerName { get; set; }
}

public class Card
{
    public string CardName { get; set; }
    public string AccountName { get; set; }
}

public List<Account> Accounts { get; set; }
public List<Card> Cards { get; set; }

//OLD
public async Task<string[]> FindAccounts(string customer)
{
    return await Task.Run(() =>
    {
        return Accounts.Where(a => a.CustomerName == customer).Select(a => a.AccountName).ToArray();
    });
}

//OLD
public async Task<string[]> FindCards(string[] accounts)
{
    return await Task.Run(() =>
    {
        return Cards.Where(c => accounts.Contains(c.AccountName)).Select(a => a.CardName).ToArray();
    });
}

//NEW
public async Task<string[]> FindCards(Task<string[]> findAccountsTasks)
{
    return await Task.Run(async () =>
    {
        var accounts = await findAccountsTasks;
        return Cards.Where(c => accounts.Contains(c.AccountName)).Select(a => a.CardName).ToArray();
    });
}

//NEW
public async Task<string[]> FindCards(string customer)
{
    return await await FindAccounts(customer).ContinueWith(FindCards);
}

private async void button7_Click(object sender, EventArgs e)
{
    Accounts = new List<Account>
    {
        new Account {CustomerName = "Tomas", AccountName = "TomasAccount1"},
        new Account {CustomerName = "Tomas", AccountName = "TomasAccount2"},
        new Account {CustomerName = "Tomas", AccountName = "TomasAccount3"},
        new Account {CustomerName = "John", AccountName = "JohnAccount1"}
    };

    Cards = new List<Card>
    {
        new Card {AccountName = "TomasAccount1", CardName = "TomasAccount1Card1"},
        new Card {AccountName = "TomasAccount1", CardName = "TomasAccount1Card2"},
        new Card {AccountName = "TomasAccount1", CardName = "TomasAccount1Card3"},
        new Card {AccountName = "TomasAccount1", CardName = "TomasAccount2Card1"},
        new Card {AccountName = "JohnAccount1", CardName = "JohnAccount1Card1"},
        new Card {AccountName = "JohnAccount1", CardName = "JohnAccount1Card2"},
    };

    var customers = new List<string> { "Tomas", "John" }.ToArray();

    //OLD
    var accountstasks = customers.Select(FindAccounts);
    var accounts = await Task.WhenAll(accountstasks);

    var cardTasks = accounts.Select(FindCards);
    var cards = await Task.WhenAll(cardTasks);

    //NEW
    cardTasks = customers.Select(FindCards);
    cards = await Task.WhenAll(cardTasks);
}

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