Reputation: 11387
When performing a long running operation I noticed that i could kickstart a long running sub-operation right off the start line and do other stuff while it fetches results from caches/databases.
The given operation is:
public async Task<Fichaclis> Finalize()
{
using (TransactionScope transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
transactionTimer.Start();
var agendasTransitionTask = ExecuteAgendas();
... DO ALOT OF SYNC OPERATIONS ...
await agendasTransitionTask;
transaction.Complete();
}
}
private Task ExecuteAgendas()
{
return ags.GetAgendas().ContinueWith((prev) =>
{
var currentAgendas = prev.Result;
foreach (var item in currentAgendas)
{
... DO QUICK SYNC STUFF...
}
return ags.BulkEditAgenda(currentAgendas);
});
}
GetAgendas is a method used all over with the following signature:
public async Task<List<Agendas>> GetAgendas()
because it's widely used, i believe the problem is not there. As for BulkEditAgenda
:
public async Task BulkEditAgenda(IEnumerable<Agendas> agendas)
{
if (agendas == null || agendas.Count() == 0)
{
return;
}
var t1 = AddOrUpdateCache(agendas);
var t2 = Task.Factory.StartNew(() =>
{
try
{
foreach (var item in agendas)
{
EditNoReconnection(item);
}
Save();
}
catch (Exception ex)
{
//log
throw;
}
});
await Task.WhenAll(t1, t2);
}
EditNoReconnect
and Save
are both sync methods.
private Task AddOrUpdateCache(IEnumerable<Agendas> agendas)
{
var tasks = new List<Task>();
foreach (var item in agendas)
{
tasks.Add(TryGetCache(item)
.ContinueWith((taskResult) =>
{
...DO QUICK SYNC STUFF...
})
);
}
return Task.WhenAll(tasks);
}
TryGetCache
is also a widely used method, so I think it's safe... it's signature is private Task<AgendasCacheLookupResult> TryGetCache(
So, resuming the issue at hand: For a small set of items do in the sync session of the Finalize
method the command transaction.Complete()
is execute before Save()
(inside BulkEditAgendas). For a regular or large amount of items, it works as expected.
This means that i'm not chaining the Tasks
correctly, or my understanding of how Async/Await + Tasks/ContinueWith works is fundamentally incorrect. Where am I wrong?
Upvotes: 4
Views: 245
Reputation: 101443
The problem is most likely here:
private Task ExecuteAgendas()
{
return ags.GetAgendas().ContinueWith((prev) =>
{
var currentAgendas = prev.Result;
foreach (var item in currentAgendas)
{
... DO QUICK SYNC STUFF...
}
return ags.BulkEditAgenda(currentAgendas);
});
}
First, what you return from this is the continuation task (result of ContinueWith
). But body of ContinueWith
ends when you do
return ags.BulkEditAgenda(currentAgendas);
So body of continuation ends potentially before BulkEditAgenda
task is completed (you don't wait in any way for completion of BulkEditAgenda
). So this line
await agendasTransitionTask;
Returns while BulkEditAgenda
is still in progress. To clarify even more, note that what is returned from ExecuteAgendas
is Task<Task>
and result of await agendasTransitionTask
is Task
which represents your running BulkEditAgenda
.
To fix, just use async\await like you do everywhere else:
private async Task ExecuteAgendas() {
var currentAgengas = await ags.GetAgendas();
foreach (var item in currentAgendas) {
// do stuff
}
await ags.BulkEditAgenda(currentAgendas);
}
Upvotes: 6