Blocking main thread when first navigate to page

c# entity-framework-core performance user-interface uwp

Question

Problem

When the app starts, an empty Home page opens. I go to the Vendors tab and it freezes for a few seconds (I can see that the UI is frozen). After that, the Vendors page is displayed. The elements contain data from the database.

You might think that the lock was caused by loading data from the database. But it occurs in the async/await methods.

If after one loaded of Vendors to return to Home and back to Vendors - there is no freeze. The page opens almost instantly. In this case, the data loading mechanism also occurs (by the Loaded event)

It turns out that the freeze is only from a clean start when first visit the Vendros page. What could be the problem?

GIF

enter image description here

Code

View

<i:Interaction.Behaviors>
    <ic:EventTriggerBehavior EventName="Loaded">
        <ic:InvokeCommandAction Command="{x:Bind ViewModel.PageLoadedCommand}" />
    </ic:EventTriggerBehavior>
</i:Interaction.Behaviors>

VendorsViewModel

public ICommand PageLoadedCommand {get;}
private async void OnPageLoaded(RoutedEventArgs args)
{
    await VendorsService.InitializeAsync();

    BankItems = VendorsService.BankItems;
    AuthorityItems = VendorsService.AuthorityItems;
    VendorItems = VendorsService.VendorItems;
}

public ObservableCollection<VendorModel> VendorItems {get; set;}
public ObservableCollection<TwoLinesModel> BankItems {get; set;}
public ObservableCollection<TwoLinesModel> AuthorityItems{get; set;}

VendorService

private TwoLinesService<BankEntity> BankService { get; } = new TwoLinesService<BankEntity>();
private TwoLinesService<AuthorityEntity> AuthorityService { get; } = new TwoLinesService<AuthorityEntity>();

public ObservableCollection<VendorModel> VendorItems { get; private set; }
public ObservableCollection<TwoLinesModel> BankItems { get; private set; }
public ObservableCollection<TwoLinesModel> AuthorityItems { get; private set; }

public async Task InitializeAsync()
{
    await BankService.InitializeAsync();
    await AuthorityService.InitializeAsync();

    VendorItems = new ObservableCollection<VendorModel>
    {
        new VendorModel { Name = "Name 1", Surname = "Surname 1", Patronymic = "MiddleName 1", IssueDate = new DateTime(2020, 2, 9) },
        new VendorModel { Name = "Name 2", Surname = "Surname 2", Patronymic = "MiddleName 2", IssueDate = new DateTime(2019, 3, 7) }
    };
    BankItems = BankService.Items;
    AuthorityItems = AuthorityService.Items;

    await Task.CompletedTask;
}

TwoLinesService

public class TwoLinesService<TEntity> 
    where TEntity : TwoLinesEntity, new()
{
    private IDataServiceFactory<TEntity> DataServiceFactory { get; } = new DataServiceFactory<TEntity>();
    public ObservableCollection<TwoLinesModel> Items { get; } = new ObservableCollection<TwoLinesModel>();

    public async Task InitializeAsync()
    {
        using var dataService = DataServiceFactory.Create();

        foreach (var entity in await dataService.SelectAll())
        {
            Items.Add(CreateModel(entity));
        }

        await Task.CompletedTask;
    }

    public TwoLinesModel CreateModel(TEntity source)
    {
        return new TwoLinesModel
        {
            ID = source.ID,
            Title = source.Title,
            Text = source.Text,
            CreatedOn = source.CreatedOn,
            LastModifiedOn = source.LastModifiedOn
        };
    }
}

DataServiceFactory

public class DataServiceFactory<T> : IDataServiceFactory<T> where T : DomainEntity
{
    public IDataService<T> Create()
    {
        return Configuraiton.Current.DataProvider switch
        {
            DataProviderType.SQLite => new SQLiteService<T>(Configuraiton.Current.SQLiteConnectionString),
            _ => throw new NotImplementedException()
        };
    }
}

SelectAll()

public partial class GenericDataService<T> : IDataService<T> where T : DomainEntity
{
    private readonly DbContext _dbContext;
    public GenericDataService(DbContext dbContext) => _dbContext = dbContext;

    public async Task<IList<T>> SelectAll()
    {
        return await _dbContext.Set<T>().ToListAsync();
    }
}
1
0
3/7/2020 11:21:31 AM

Accepted Answer

Description

I managed to find a bottleneck. The program is an early stage of development. During startup, the program does not call methods associated with the database. I detected with help Debug Output that the first time calls DataServiceFactory.Create() is loaded new components. Immediately after this, the user interface freeze ends.

"ProgramName.exe" (CoreCLR: CoreCLR_UWP_Domain). Loaded "C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.27909.0_x64__8wekyb3d8bbwe\System.Threading.Tasks.Extensions.dll".
"ProgramName.exe" (CoreCLR: CoreCLR_UWP_Domain). Loaded "C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.27909.0_x64__8wekyb3d8bbwe\System.Linq.Expressions.dll".
"ProgramName.exe" (CoreCLR: CoreCLR_UWP_Domain). Loaded "C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.27909.0_x64__8wekyb3d8bbwe\System.Resources.ResourceManager.dll".
"ProgramName.exe" (CoreCLR: CoreCLR_UWP_Domain). Loaded "C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.27909.0_x64__8wekyb3d8bbwe\System.Diagnostics.Tracing.dll".
"ProgramName.exe" (CoreCLR: CoreCLR_UWP_Domain). Loaded "C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.27909.0_x64__8wekyb3d8bbwe\System.ComponentModel.TypeConverter.dll".
"ProgramName.exe" (CoreCLR: CoreCLR_UWP_Domain). Loaded "Anonymously Hosted DynamicMethods Assembly". 
"ProgramName.exe" (CoreCLR: CoreCLR_UWP_Domain). Loaded "C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.27909.0_x64__8wekyb3d8bbwe\System.Data.Common.dll".
"ProgramName.exe" (CoreCLR: CoreCLR_UWP_Domain). Loaded "C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.27909.0_x64__8wekyb3d8bbwe\System.ComponentModel.Primitives.dll".
"ProgramName.exe" (CoreCLR: CoreCLR_UWP_Domain). Loaded "C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.27909.0_x64__8wekyb3d8bbwe\System.Transactions.Local.dll".
"ProgramName.exe" (CoreCLR: CoreCLR_UWP_Domain). Loaded "C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.27909.0_x64__8wekyb3d8bbwe\System.ComponentModel.Annotations.dll".
"ProgramName.exe" (CoreCLR: CoreCLR_UWP_Domain). Loaded "C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.27909.0_x64__8wekyb3d8bbwe\System.Threading.Timer.dll".
"ProgramName.exe" (CoreCLR: CoreCLR_UWP_Domain). Loaded "C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.27909.0_x64__8wekyb3d8bbwe\System.Threading.Thread.dll".
"ProgramName.exe" (CoreCLR: CoreCLR_UWP_Domain). Loaded "C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.27909.0_x64__8wekyb3d8bbwe\System.Text.RegularExpressions.dll".
"ProgramName.exe" (CoreCLR: CoreCLR_UWP_Domain). Loaded "C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.27909.0_x64__8wekyb3d8bbwe\System.Buffers.dll".
"ProgramName.exe" (CoreCLR: CoreCLR_UWP_Domain). Loaded "C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.27909.0_x64__8wekyb3d8bbwe\System.Memory.dll".

Solution

The main solution is to load data at startup. At this stage, there is nothing to load.

I find two temporary solutions.

  • Call the factory creation in the App.xaml.cs. This loads most of the components.
var dataServiceFactory = new DataServiceFactory<VendorEntity>();
var dataService = dataServiceFactory.Create();
  • Or initiate a start in the event handler OnPageLoaded() using Task.Run()
private async void OnPageLoaded(RoutedEventArgs args)
{
    await Task.Run(() => VendorsService.InitializeAsync());
    ...
}
0
3/7/2020 11:30:03 AM

Popular Answer

That might be a common pitfall in asynchronous programming on UWP. To use async/await/Task is not equivalent to run the code in background thread. If you'd like to do some time-consuming task in the background thread, you need to wrap it in Task.Run(). Or otherwise UI-thread will be blocked.

Try using Task.Run in the SelectAll() in anyway.

public Task<IList<T>> SelectAll()
{
    return Task.Run(async ()=> { return await _dbContext.Set<T>().ToListAsync(); });
}


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow