2017-02-21 28 views
0

У меня есть следующий сценарий:Как использовать асинхронный/Await в сценарии с несколькими задачами, с каждой задачей, требующей новый асинхр/ждет

Клиент имеет ряд счетов, каждый из них имеет ряд карт, привязанные.

Теперь у меня есть запрос, где мне нужно запрашивать учетные записи с карточками у нескольких клиентов. У меня есть асинхронные методы для запроса учетных записей и карточек отдельно, то есть FindAccounts(string customer), FindCards(string[] accounts).

Так что у меня этот метод:

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)   
    ... 
} 

В то время как это будет работать у него есть проблема, которую вы должны ждать счетов всех клиентов, которые будут закончены до карты могут быть запрошены. Более эффективная реализация будет продолжаться и запрашивать карты для учетных записей клиентов, как только завершатся учетные записи для конкретного клиента (не дожидаясь других клиентов).

Мой вопрос в том, можно ли это сделать с async/await. Я думаю, что смогу справиться с ContinueWith, но я не на 100% уверен, что все в порядке, чтобы смешать async/await с подходом ContinueWith.

+0

Вы правы, ContinueWith звучит так, как вы хотите. Вы создадите большую задачу «Получите учетную запись, а затем получите карты» для каждой учетной записи. – Juan

+0

Как насчет смешивания двух подходов? Знаете ли вы о каких-либо недостатках? –

+0

Да, я имел в виду смешение, вы можете ждать на ContinueWith, в основном вы можете ждать во всем, что возвращает Task и ContinueWith. Если вы покажете свои сигнатуры методов, я могу предоставить код oyu пример – Juan

ответ

3

Это может быть более разумным, чтобы разделить его клиентом и асинхронном в том, что:

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) 
    … 
} 

Однако стоит учесть баланс эффективности, как ваш FindAccounts и FindCards работы. Например. если они работают как одиночный SELECT, оттесненный в базу данных, тогда большая параллельность превращения его в несколько меньших бит работы может не стоить большего объема накладных расходов. Это может быть так, что ожидание 20 или даже 200 результатов будет лишь незначительно медленнее, чем ждать 1, а затем разбить на 20 запросов очень мало, даже до того, как будут рассмотрены дополнительные подключения.

+0

Спасибо, сейчас это кажется довольно очевидным, но я не думал об этом сам по какой-то причине. @Panagiotis Kanavos отметили то же самое решение в комментариях. Причина, по которой у меня нет одного SELECT, заключается в том, что я не контролирую хранилище данных и допускаю только эти фрагментированные запросы. Я бы предпочел расспросить их всех, если бы у меня был шанс :) –

+0

Если ваши 'FindAccounts' совершают несколько вызовов за кулисами, то шансы, конечно же, лучше, что это улучшит ситуацию или, по крайней мере, не сделает их хуже. –

+0

'FindAccounts' делает только один вызов, но проблема в том, что мне приходилось находить счета для ВСЕХ клиентов, прежде чем я смог продолжить их карты :) –

0

Трудно сказать, поможет ли вам подход tasks per customer, лучший способ узнать это - провести тест по вашему сценарию.

Я сделал простой (событие, которое может показаться довольно запутанным), например, как можно изменить первый подход, чтобы можно было выполнять задачи на каждого клиента.
Вы упомянули, что можете справиться с этим, я просто хотел опубликовать его здесь, чтобы любой, кто интересуется этим, может сыграть с ним.

Я использовал Task.Run(...) для имитации задач async.

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); 
} 
+0

Спасибо за ваше предложение, видимо, было решение, которое еще проще и понятнее :) –