Leonardo
Leonardo

Reputation: 11387

Async/Await - Await not holding as expected

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

Answers (1)

Evk
Evk

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

Related Questions