Reputation: 30175
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, i.e. 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.
Upvotes: 0
Views: 534
Reputation: 113222
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. E.g. 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.
Upvotes: 3
Reputation: 3019
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);
}
Upvotes: 0