efcore: Deadlock in EntityFrameworkQueryableExtensions.ToListAsync if used with synchronization context

 public async Task<ActionResult> Index()
        {
            var ctx = GetDbContext();
            var result = ctx.Set<GetServicePlanConfigurationByCurrencyResult>().FromSqlRaw("select 1").ToListAsync().ConfigureAwait(false).GetAwaiter().GetResult();
            
            return View();
        }

This code deadlocks inside ToListAsync()

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 16 (9 by maintainers)

Most upvoted comments

Thanks @ErikEJ, @roji for you help, seems problem solved. PowerTools PR merged, .vsix released. We tested generation (I work with Roman), everything looks good. No more sync thru async, code is clean & nice (for the legacy project that is in mid-migration).

When we migrate to EF Core & after that migrate to .NET we’ll be able to upgrade to latest EF Core & go async way. But for now sync looks like a perfect step forward.

EF Core 3.1 doesn’t support sync via async mode

I’ll repeat - sync-over-async is discouraged always - regardless of ConfigureAwait(false) - so this isn’t an EF Core thing. Even with EF Core 3.1, doing sync-over-async can cause various starvation issues and problems. Do not do this.

When talking about big projects it always good to be able to move step by step: migrate to EF Core while on Framework, do sync via async, and then slowly refactor code, endpoint by endpoint.

That is a specifically-discouraged practice: you should not gradually convert an application by using async in certain places and then doing sync-over-async over them. See this particular point in Async all the way.

What you can do, is gradually convert sync to async “vertically”, i.e. ensure that complete call stacks are fully async, but leaving others sync (as long as they don’t interact). That allows a gradual transition.

Maybe backporting your commit to 3.1 makes sense?

Adding ConfigureAwait(false) everywhere is a big change that could have various consequences; it isn’t safe to backport such a thing to a 3.1 patch version.

@raman-kazhadub EF Core 3.1 didn’t have ConfigureAwait(false) internally, by design; EF Core may call into user code (i.e. when materializing entities), and at the time we thought it was important to maintain the user’s synchronization context for data binding scenarios (i.e. in case the entity property being set calls into a UI action). We changed this in EF Core 5, but still avoid doing ConfigureAwait(false) in the specific code flow of SaveChanges for the above reason.

In other words, the guidance to use ConfigureAwait(false) is definitely right, but isn’t absolute - there are some cases where exceptions makes sense.

In any case, this again isn’t directly related to the guidance against synchronously blocking on asynchronous code, as you’re doing above. Even with a library that contains ConfigureAwait(false) everywhere, doing this could cause thread pool starvations and is a very bad idea.

The strategy in EF Core is to have 2 separate implementations (sync + async) and usage of async in sync way in .NET Framework is not supported (due to lack of .ConfigureAwait(false))

It’s worth nothing that this has nothing to do with EF Core. In .NET in general, you should always avoid synchronously blocking on an asynchronous operation (this is known as sync-over-async, read e.g. this). @davidfowl @roji

one point from this article:

In your “library” async methods, use ConfigureAwait(false) wherever possible.

ToArrayAsync contains ConfigureAwait(false): https://github.com/dotnet/efcore/blob/main/src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs#L2244 ToListAsync doesn’t: https://github.com/dotnet/efcore/blob/main/src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs#L2209

Also there is the same problem for ExecuteSqlRawAsync (EF Core 3.1.25) because ConfigureAwait(false) is missing: https://github.com/dotnet/efcore/blob/v3.1.25/src/EFCore.Relational/Extensions/RelationalDatabaseFacadeExtensions.cs#L656

As I can see, @roji fixed it in the main branch(https://github.com/dotnet/efcore/pull/21110/commits/645a889460d781d3c2c8d2b299488554eb8f7d4e), but this commit is not merged to 3.1.* (latest available for .NET Framework). Also fix for ToListAsync is missing in this commit

So there are bugs for ToListAsync and ExecuteSqlRawAsync in EF Core 3.1.25 because ConfigureAwait(false) missing.