16

I have a loop and within the loop I'm doing:

await Task.Delay(1000, ct).ContinueWith(async _ =>
{
    await SecondMethodAsync(ct);
});

The second method gets an entity using EF, sets some properties and saves the entity back to the datastore by calling await SaveChangesAsync() on the DbContext.

The above should wait for 1s and then continue with the second method. With the above implementation I get the following exception:

A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.

When I change the above to:

await Task.Delay(1000, ct);
await SecondMethodAsync(ct);

Everything works fine.

What is the difference with the above 2 snippets and how do I get to make the first snippet work?

Ivan-Mark Debono
  • 15,500
  • 29
  • 132
  • 263
  • 2
    How you have defined the loop? If you are using `enumerable.ForEach` then it can create the issue as mentioned at https://stackoverflow.com/questions/40363807/a-second-operation-started-on-this-context-before-a-previous-asynchronous-operat – user1672994 Jul 12 '22 at 05:06
  • Just a simple `for (int i = 0; i < max; i++) {}` – Ivan-Mark Debono Jul 12 '22 at 05:10
  • 3
    I think your answer is [here](https://stackoverflow.com/questions/18697167/use-an-async-callback-with-task-continuewith) – Michalor Jul 12 '22 at 05:33
  • 1
    As a side note, your code violates this guideline: [Do not create tasks without passing a TaskScheduler](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2008). Also are you sure that you want to call the `SecondMethodAsync` even when the `Task.Delay` has been canceled? – Theodor Zoulias Jul 12 '22 at 05:44
  • 2
    Issue is that EF Core do not support "simultaneous" operations on same `DbContext` instance. If you want to execute db operations "simultaneously" create different DbContext instance for every operation (use DbContextFactory) – Fabio Jul 12 '22 at 06:59

2 Answers2

12

From A Tour of Task, Part 7: Continuations by the async/await guru Stephen Cleary:

In conclusion, I do not recommend using ContinueWith at all, unless you are doing dynamic task parallelism (which is extremely rare). In modern code, you should almost always use await instead of ContinueWith. There are several benefits to await.

You know the solution and it's great:

await Task.Delay(TimeSpan.FromSeconds(1), ct);
await SecondMethodAsync(ct);
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
tymtam
  • 31,798
  • 8
  • 86
  • 126
10

I think the reason is the following. Please correct me anybody if I am wrong.

The async lambda inside ContinueWith returns Task, which means the ContinueWith is actually done right after it gets that Task (not when the Task is completed). The program flow goes right into the next loop iteration and hitting that DBContext again.

So the following might also work since the return type of ContinueWith is Task<Task>:

await await Task.Delay(1000, ct).ContinueWith(async _ => {
    await SecondMethodAsync(ct); 
});
Michalor
  • 363
  • 3
  • 14