Home > Blockchain >  async Task is being executed immediately instead of asynchronously
async Task is being executed immediately instead of asynchronously

Time:05-14

I have a Task that should execute asynchronously that is executing synchronously. I'm not entirely sure why it's happening, and it seems to follow the example from Microsoft's TAP model doc

    private T payload;
    private Func<Task<T>> updateFn;
    private Task updateTask = null;

    public async Task<TResult> Get<TResult>(bool force = false)
    {
        updateTask = Update();
        await updateTask;
        return payload;
    }

    private async Task Update()
    {
        try {
            payload = await updateFn();
            lastFetch = timeService.UTCNow;
            logger.Info($"Updated on {timeService.Now}");
        } catch (Exception e) {
            throw e;
        } finally {
            updateTask = null;
        }
    }
// ... 
// the updateFn is of type: async () => await networkRequestFunction

so what I expect to happen is ( // #1 indicates order of execution )

public async Task<TResult> Get<TResult>(bool force = false)
{
    updateTask = Update(); // #1, #3 - updateTask set to Update
    await updateTask; // #4
    return payload; // #6
}

private async Task Update()
{
    try {
        payload = await updateFn(); // #2
        lastFetch = timeService.UTCNow; // #5...
        logger.Info($"Updated on {timeService.Now}");
    } catch (Exception e) {
        throw e;
    } finally {
        updateTask = null; // #5 ends
    }
}

but what I'm observing is

public async Task<TResult> Get<TResult>(bool force = false)
{
    updateTask = Update(); // #1, #4 updateTask is set to Update, which is already completed
    await updateTask; // #5
    return payload; // #6
}

private async Task Update()
{
    try {
        payload = await updateFn(); // #2
        lastFetch = timeService.UTCNow; // #3...
        logger.Info($"Updated on {timeService.Now}");
    } catch (Exception e) {
        throw e;
    } finally {
        updateTask = null; // #3 ends
    }
}

So what should happen is when calling Update, it should return with a Task after Update hits await updateFn(). But it's completing as if await updateFn() is a synchronous call.

The issue with this unexpected behavior is that updateTask = null executes first, and then it is set to updateTask = Update() (the completed task). But I need the other way around to happen.

Note - this only happens sometimes. This is for a caching of network payloads, and other instances of this class work fine. it's just for one specific payload that it doesn't. the updateFn of that payload consists of multiple await networkRequest.

CodePudding user response:

This is the correct behaviour, cause after await you are not returning any task, so the flow of the Update() method continues. that's why your #3 is in the lastFetch asignation line, not on the first line on Get<TResult>.

If you want to return and finish earlier, your Update method should look like this, assuming that UpdateFn() returns a Task too:

private async Task Update()
{
    try {
        return await updateFn(); // #2
    } catch (Exception e) {
        throw e;
    }
}

You can also refactor your Get<TResult> method to be something like:

public async Task<TResult> Get<TResult>(bool force = false)
{
    return await Update();
}

CodePudding user response:

I have a Task that should execute asynchronously that is executing synchronously.

Asynchronous methods may execute asynchronously. They don't have to execute asynchronously. Every async method begins executing synchronously (as explained on my blog), and is only asynchronous if it awaits a task that is not already complete. If it never awaits or if it awaits only already-completed tasks, then the method will run fully synchronously. This is normal behavior for C#.

Ideally you should structure the code so that Update isn't responsible for clearing its own returned task. I mean, that's kinda weird: a method is changing the variable holding the value it already returned. Something like this, maybe:

private T payload;
private Func<Task<T>> updateFn;
private Task updateTask = null;

public async Task<TResult> GetAsync<TResult>(bool force = false)
{
  await UpdateAsync();
  return payload;
}

private async Task UpdateAsync()
{
  try {
    updateTask = DoUpdateAsync();
    await updateTask;
  } finally {
    updateTask = null;
  }

  static async Task DoUpdateAsync()
  {
    payload = await updateFn();
    lastFetch = timeService.UTCNow;
    logger.Info($"Updated on {timeService.Now}");
  }
}

This way, UpdateAsync both sets and clears updateTask, and the wrapped DoUpdateAsync just contains the logic.

  • Related