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 await
s a task that is not already complete. If it never await
s or if it await
s 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.