Sanity check needed, as I'm still very unfamiliar with the nuances behind async/await and how they relate to more traditional Task / TPL code.
I have a high-level repository layer which is assembling a business object from various lower-level repositories. As a business decision, we are doing keyed lookups at the data layer and assembling the composite object in code. In the following code, "Account" is the business object, and the various _tAccountXXXRepo objects are all patterned similarly to this (we are using NPoco):
public class T_AccountMapperRepository : BaseNPocoRepository<T_AccountMapper>
, IRetrieveMany<T_AccountMapper>
, IRetrieve<AccountId, T_AccountMapper>
{
public T_AccountMapperRepository(IDatabase database) : base(database)
{
}
public async Task<T_AccountMapper> Retrieve(AccountId input)
{
return await Database.QueryAsync<T_AccountMapper>()
.First(x => x.AccountId == input)
.ConfigureAwait(false);
}
}
The code to fetch the different values can be logically executed in parallel, and I am wondering if this implementation is the correct pattern to do so: (the various methods being called on the Account object are thread-safe as well)
public async Task<Account> Retrieve(AccountId input1, DateTime input2)
{
var account = new Account();
await Task.WhenAll(
_tAccountMapperRepo.Retrieve(input1)
.ContinueWith(async result => account.SetAccountMap(await TranslateAccountMap(result))),
_tPaymentRedirectionRepo.Retrieve(input1, input2)
.ContinueWith(async result => account.ChangePayerToAccount(await TranslatePaymentRedirection(result))),
_tAccountAncestorRepo.Retrieve(input1, input2)
.ContinueWith(async result => await _tAccountMapperRepo.Retrieve((await result).AncestorId))
.ContinueWith(async result => account.MoveAccountToNewParentAccountMap(await TranslateAccountMap(await result))),
_tAccountRepo.Retrieve(input1)
.ContinueWith(async result => await _tAccountTypeRepo.Retrieve((await result).TypeId))
.ContinueWith(async result => account.SetAccountType(await TranslateAccountType(await result)))
);
return account;
}
Any method that is labelled with TranslateXXX all look similar to this:
private static async Task<AccountMap> TranslateAccountMap(Task<T_AccountMapper> mapTask)
{
if (!mapTask.IsCompletedSuccessfully)
throw new InvalidOperationException(nameof(mapTask), mapTask.Exception);
var map = await mapTask;
return new AccountMap(map.AccountId, map.Login, map.Namespace);
}
My main concerns are mixing Task and async/await, and whether or not my async & awaiting is re-introducing an element of synchronicity in what I'm hoping to make a very asynchronous process. My end goal is that as many various properties as possible are fetched in parallel and assembled
resultin yourContinueWithdelegates. They are really misleading. Because you haven't specifiedTaskContinuationOptions.OnlyOnRanToCompletionthat's why your ancestor / antecedent task could have failed. IMO, calling a failed task asresultis misleading. \$\endgroup\$