0

I have 2 projects, both use Net 5, entity framework Net 5 and async. The unique difference is that the project that is blocked use Sql Server and the other use Sqlite. But I guess the database is not the reason.

Porject 1, that is blocked:

public void Main()
{
    SqlServerEFNet5 miRepository = new SqlServerEFNet5();
    miRepository.GetAllMyRowsAsync().GetAwaiter().GetResult();
}


public Task<List<MyType> GetAllMyRowsAsync()
{
    using (ContextEfNet5 dbContext = new ContextEfNet5(_optionsDbContext))
    {
        return await dbContext.MyEntities.FromSqlRaw("Select * from MyType").ToListAsync().ConfigureAwait(false);
    }
}

My project 2, it is not blocked:

private void Main()
{
    MyViewModelProperty.AddRange(Service.Service.GetAllMyRowsAsync().GetAwaiter().GetResult()));
}


public static async Task<List<MyType>> GetAllMyRowsAsync()
{
    using (Context myDbContext = new Context())
    {
        return await myDbContext.MyType.ToListAsync().ConfigureAwait(false);
    }
}

Really in my second project I don't use raw sql query, but I have tried use only pure linq in my first project and still is blocked.

Also, in my first project I don't populate a collection of the view model, that it could be affect because it is used by the view, but in my second project I populate a collection of the view model, so it is not the problem.

I don't understand why in one case it is blocked and in another case isn't.

Edit 01:

If I use this code in the project 1, it is not blocked:

        Task<int> miTask = Task<int>.Run(() => { return 0; });

        int myIntResult = miTask.GetAwaiter().GetResult();

Edit 02: the suggestion solution of another question doesn't help me because it suggest to use await, but I can't use it because I have to call this async code in the constrcutor, and a constructor can't be async.

Edit 03: I have found in this How would I run an async Task<T> method synchronously? a helper that it works.

Álvaro García
  • 18,114
  • 30
  • 102
  • 193
  • 1
    One projects runs in an environment with a `SynchronizationContext`, and the other one [without](https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html)? – GSerg Dec 10 '20 at 15:26
  • 2
    Why don't you just use `async Task Main()` and get rid of the `GetAwaiter().GetResult()`? – Peter Csala Dec 10 '20 at 15:26
  • I will check about environment with a SynchronizationContext. But in neither both projects I set this. About why don't use async task it is because I am using this code in the constructor of the class, and in another methdos that are event handler and can't be async. – Álvaro García Dec 10 '20 at 15:37
  • @GSerg in the link you give me, he tells I can use GetAwaiter() .GetResult() with no worry about deadlocks, but it is what I am doing in both cases. – Álvaro García Dec 10 '20 at 15:40
  • @Liam Thanks for the suggestion, but it doesn't help in my case. In both projects, I use the GetAwaiter() in the constructor of the main class, and in the first case is blocked and in the second is not. – Álvaro García Dec 10 '20 at 16:11
  • My assumption is that it might have to do with one project not having `ConfigureAwait(false)` all the way. Could you in the first project test if it works if you add this at the start of the `GetAllMyRowsAsync` method: `await Task.Delay(50).ConfigureAwait(false);`? (Please don't use that as a solution if it works, it's just a test to confirm the problem). Important that it's done at the start, before the other `async` call! –  Dec 10 '20 at 16:13
  • Which one uses sqlLite and which one Sql Server? It actually metters cause EF Providers for them are different .ConfigureAwait(false) NOT ALWAYS helps, if somewhere down the line EF/Micrsoft uses old event-based async pattern and converts it to TPL - .ConfigureAwait(false) will not work, Cause event-based pattern will always capture context. Read this - https://learn.microsoft.com/en-us/archive/msdn-magazine/2015/july/async-programming-brownfield-async-development Section The Blocking Hack. And a reason for asking which one - i want to find source code of provider to prove the point – Nikita Chayka Dec 10 '20 at 16:18
  • @NikitaChayka The first project, that is blocked, use Sql Server, the second project that works use Sqlite. – Álvaro García Dec 10 '20 at 16:38
  • @ÁlvaroGarcía see my answer – Nikita Chayka Dec 10 '20 at 16:57

1 Answers1

2

OK, figured out. The deadlock is becuase of SynchronizationContext and it's capturing as stated many times in comments to your question. Usually solution is either use await all the way up, or ConfigureAwait(false), which you doing. BUT!

As stated here (section The Blocking Hack): https://learn.microsoft.com/en-us/archive/msdn-magazine/2015/july/async-programming-brownfield-async-development

If somewhere down the line Microsoft/EF uses conversion from old event-based async pattern then ConfigureAwait(false) will have no effect, cause context will be already captured anyway.

And turns out they do such conversion, here is the source of SqlCommand for SqlServer:

https://github.com/dotnet/SqlClient/blob/bd89b6433892214eed41e97afb0a9430d11d4681/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs

Look there for line 2996.

Now why SqlLite is working fine - because SqlLite doesn't not support async I/O, so all asyncs for SqlLite are fake, you could clearly see it in source of SqliteCommand:

https://github.com/dotnet/efcore/blob/main/src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs

Line 406.

And explanation for "fake" is here - https://learn.microsoft.com/en-us/dotnet/standard/data/sqlite/async

Nikita Chayka
  • 2,057
  • 8
  • 16