6

I'm experimenting with async await and I'm encountering UI blocking that shouldn't be happening.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        LoadButton.Click += LoadButton_OnClick;
    }

    private async void LoadButton_OnClick(object sender, RoutedEventArgs e)
    {
        LoadButton.IsEnabled = false;

        // await Task.Delay(2000);

        using(TestContext ctx = new TestContext())
        {
            IList<User> users = await ctx.Users.ToListAsync();
        }

        LoadButton.IsEnabled = true;
    }
}

If I comment the DbContext bit and uncomment Task.Delay, it behaves as expected - non blocking the UI.

Based on my understanding, the ToListAsync() method is still invoked from the UI thread but shouldn't block it. Even if the method is CPU-bound (probably isn't) it would cause lag, not a complete block.

My questions:

Do I understand this correctly?

Why is my UI blocking while awaiting on ToListAsync() ?


EDIT

I tried doing a database call before calling this method to warm everything and ensure it doesn't block on establishing the first connection. Also I tried adding a couple of thousand entries to the DbSet and awaiting on SaveChangesAsync and the same thing happens - the UI freezes completely for several seconds.


EDIT 2

I tried another example with the same code and it seems to be working. The difference is that in first example I'm using Code First and SQL CE and in the working example Database First and SQL Server.

loodakrawa
  • 1,468
  • 14
  • 28
  • `Task.Delay(2000)` of course will block the code for 2 seconds. You should explain why you want to delay for 2 seconds. – Hopeless Sep 29 '15 at 03:54
  • @Hopeless he is testing that the UI doesn't block while awaiting. Simulating a CPU-bound task – Alex Wiese Sep 29 '15 at 03:55
  • @alexw do you mean he included that line to test it? – Hopeless Sep 29 '15 at 03:55
  • @Hopeless yeah just for testing. During the 2 seconds the UI is still responsive. When it is accessing the database it is not responsive – Alex Wiese Sep 29 '15 at 03:56
  • Can you try priming the DbContext first? Eg. is it only slow on the first click or subsequent clicks? – Alex Wiese Sep 29 '15 at 03:57
  • please clarify on how it's blocked: freezed totally, freezed for just a short time, or the button is not enabled? – Hopeless Sep 29 '15 at 03:58
  • It's completely frozen. @alexw - subsequent calls are faster but still block the UI completely – loodakrawa Sep 29 '15 at 04:04
  • 1
    Perhaps establishing a connection to your database is what's causing the freezing? Remove `ToListAsync` and just have an empty bodied `using` and see if it persists. – jamesSampica Sep 29 '15 at 04:45
  • @Shoe - I tried and it's not noticeable (except for the first call) – loodakrawa Sep 29 '15 at 05:16
  • I've tried the exact code to load 100 000 rows (just a simple projection, no join, groupby, ..) but it works perfectly on my side. No freezing at all. It takes about 2 seconds to complete the loading. However I tested with Winforms. your code looks fine. Be sure you don't have any other code involved (triggered by the clicking on the load button). I don't think WPF could have some problem while winforms does not. – Hopeless Sep 29 '15 at 05:50
  • How do you find out your UI is blocked ? Do you get the Thread.CurrentThread.ManagedThreadId to verify your "ToListAsync" running in the same thread as the GUI one ? Is that you think the UI is blocked because your LoadButton is not "Enable" right away after the aysnc task is trigger ? – cscmh99 Sep 29 '15 at 06:22
  • @Hopeless - tried another example and got it working properly. See Edit2 – loodakrawa Sep 29 '15 at 07:11
  • @cscmh99 - the window is stuck for several seconds without the ability to move resize, etc. – loodakrawa Sep 29 '15 at 07:11
  • In debug mode and during the UI FREEZE ... Pause the process from Viusal Studio. Tell us what line the breakpoint locate and probably paste the call stack ? I can see the TestContext might take up sometime to slow down the Gui thread but wondering if that's root cause – cscmh99 Sep 29 '15 at 07:52

4 Answers4

5

The connection objects used in SQL Server CE are not threadsafe (the naturally async APIs are not implemented). I believe you are encountering this issue with SQL CE as it is executing on the thread which created the DbContext and not returning control back to the UI thread when awaited. Try instantiating the DbContext within a Task and awaiting the result.

LoadButton.IsEnabled = false;

var users = await Task.Run(() => 
{
    using(TestContext ctx = new TestContext())
    {
        return ctx.Users.ToList();
    }
});

LoadButton.IsEnabled = true;
Alex Wiese
  • 8,142
  • 6
  • 42
  • 71
  • 1
    I believe thread safety has nothing to do with this example since everything is running in the same thread (UI). I'm aware how to fix this issue by queuing the work on the thread pool but the point was achieving non blocking behaviour without additional threads. – loodakrawa Sep 30 '15 at 01:28
  • @LukaSverko yes that is exactly why your UI thread is blocking. SQL Server Provider for Entity Framework 6 has implemented naturally async methods which return control back to the UI thread when awaited, SQL Server CE Provider has not. If you implement it this way it will workaround the limitations of SQL Server CE and your UI thread will not block. – Alex Wiese Sep 30 '15 at 01:37
2

It seems that The SQL Server Compact ADO.NET provider doesn't provide async APIs and that's why it's blocking. I can't find the exact source about this, but this answer clearly states so.

Community
  • 1
  • 1
loodakrawa
  • 1,468
  • 14
  • 28
1

I believe ui is blocked by this line

using(TestContext ctx = new TestContext())

Try something like this (just to check this is the problem)

await Task.Run(() => {using(TestContext ctx = new TestContext())
    {
        IList<User> users = ctx.Users.ToList();
    }})
Aleksey L.
  • 35,047
  • 10
  • 74
  • 84
  • It's not blocked by this line - I tried putting the context as a class member and instantiating it before doing any work and reusing it for every request. – loodakrawa Sep 29 '15 at 05:33
-3

await will only work with functions defined with async keyword and having Task as there return type.

In the first scenario Task.Delay(2000) will return Timespan, hence await is having no impact.

While in the second scenario ctx.Users.ToListAsync() returns a Task and after this we are calling await, which results in blocking of current(UI) thread.

For more details, see link from Microsoft.