Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task.Run blocking main thread (frozen UI)

I'm trying to make an asynchronous call to load data to my grid. Problem is, operation is blocking the UI (running in the main thread) - and i don't know why. I'm trying to make it to fetch data in the background ... Here's the code:

Binding ViewModel class to DataContext of the main window:

<Window.DataContext>
    <vm:MainWindowViewModel WindowTitle="MVVM" BtnLoadText="LOAD DATA"/>
</Window.DataContext>

DataGrid with column binding to the collection property (PeopleList) in ViewModel class:

    <DataGrid AutoGenerateColumns="False" IsReadOnly="True" ItemsSource="{Binding Path=PeopleList, Mode=TwoWay}" Margin="5">
        <DataGrid.Columns>
            <DataGridTextColumn Header="First name" Binding="{Binding Path=FirstName}"/>
            <DataGridTextColumn Header="Last name" Binding="{Binding Path=LastName}"/>
            <DataGridTextColumn Header="Age" Binding="{Binding Path=Age}"/>
        </DataGrid.Columns>
    </DataGrid>
    <Button x:Name="btn_LoadData" Margin="5" Grid.Row="1" Content="{Binding Path=BtnLoadText}" Click="btn_LoadData_Click"/>

Code-Behind of the MainWindow - running asynchronous button click event:

public partial class MainWindow : Window
{
    private MainWindowViewModel mainWindowViewModel;
    public MainWindow()
    {
        InitializeComponent();
        mainWindowViewModel = (MainWindowViewModel)DataContext;
    }

    private async void btn_LoadData_Click(object sender, RoutedEventArgs e)
    {
        await mainWindowViewModel.LoadData();
    }
}

ViewModel class responsible for MainWindow:

class MainWindowViewModel
{
    public string WindowTitle { get; set; }
    public string BtnLoadText { get; set; }

    public ObservableCollection<Person> PeopleList { get; set; }

    private Database database = new Database();

    public MainWindowViewModel()
    {
        PeopleList = new ObservableCollection<Person>();
    }

    public async Task LoadData()
    {
        PeopleList.Clear();

        var result = await database.GetPeopleListLongOperationAsync();

        PeopleList.Add(result.First());
    }
}

As you see, i'm making an asynchronous call with LoadData method, which gets data from the database and adding it to ObservableCollection, which updates DataGrid (Binding from the DataGrid)

Database class, which "emulates" data fetching:

public class Database
{
    public IEnumerable<Person> GetPeopleListLongOperation()
    {
        // forcing "long" data load
        Thread.Sleep(5000);
        yield return new Person() { FirstName = "Name", LastName = "LastName", Age = new Random().Next(18, 40) };
    }

    public Task<IEnumerable<Person>> GetPeopleListLongOperationAsync()
    {
        return Task.Run<IEnumerable<Person>>(() =>
        {
            return GetPeopleListLongOperation();
        });
    }
}

I'm using Task.Run to fetch data in background thread. The problem is - it is running in Main Thread and it's blocking the UI.

Any suggestions? I'm not sure anymore, if i understand async operations correctly ...

EDIT

Changing Task result type from IEnumerable to List made it work. Can someone explain me why?

    public Task<List<Person>> GetPeopleListLongOperationAsync()
    {
        return Task.Run<List<Person>>(() =>
        {
            return GetPeopleListLongOperation().ToList();
        });
    }
like image 938
asiesom Avatar asked Apr 01 '17 10:04

asiesom


People also ask

Does Task result block thread?

Yes Accessing Task. Result is the same as calling Task. Wait(). Accessing the property's get accessor blocks the calling thread until the asynchronous operation is complete; it is equivalent to calling the Wait method.

Does async await block UI thread?

Because await is only valid inside async functions and modules, which themselves are asynchronous and return promises, the await expression never blocks the main thread and only defers execution of code that actually depends on the result, i.e. anything after the await expression.

Does Task run Use thread pool?

request thread (ASP.NET thread) starts the GetAsync method and calls DoComplexCalculusAsync() asynchronously. Inside DoComplexCalculusAsync(), Task. Run uses another new thread from thread pool to do the heavy calculations in the background.

Does Task run use a new thread?

NET code does not mean there are separate new threads involved. Generally when using Task. Run() or similar constructs, a task runs on a separate thread (mostly a managed thread-pool one), managed by the . NET CLR.


1 Answers

Forget about the threading complication for now, and just take this code:

public IEnumerable<Person> GetPeopleListLongOperation()
{
    // forcing "long" data load
    Thread.Sleep(5000);
    yield return new Person();
}

When you call GetPeopleListLongOperation() it will return immediately, it won't wait 5 seconds. This is because iterator blocks like this are lazily evaluated; Thread.Sleep(5000) will only be called when you enumerate through the sequence.

Now you understand that, you should see that you 'fix' works because ToList will enumerate the sequence to completion while still on the thread pool thread and return the cached results. Without this you were enumerating the sequence on the UI thread on the call to result.First() and blocking it.

like image 78
Charles Mager Avatar answered Nov 03 '22 00:11

Charles Mager