Search code examples
c#xamlwindows-runtimewindows-phonewinrt-xaml

How to run codes on background thread in Windows Runtime


I'm using incremental loading to show a ListView items. I run LoadDetails method in the background thread using Task.Run(...) to not busy the UI thread.

But it still blocks the UI thread and it doesn't render UI elements until it finishes the task.

executing LoadDetails method takes around 3 seconds to complete.

private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
{
    if (args.Phase != 6)
    {
        throw new Exception("Not in phase 6");
    }

    var item = args.Item as ItemModel;

    var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
    var textBlock = (TextBlock)templateRoot.FindName("textBlock");

    await Task.Run(() => LoadDetails(textBlock, item.Id));
}

private async Task LoadDetails(TextBlock textBlock, string id)
{
    int count = await DataSource.GetItemCounts(id);

    await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            textBlock.Text = count.ToString();
        });
}

How to fix this so it doesn't block the UI thread? thanks.

(It's a Windows Phone Runtime app)


Solution

  • It's not clear from your question how you are measuring the 3 second delay. Is it that the call to GetItemCounts() itself takes 3 seconds? If so, isn't that to be expected? The delay is why you would execute that asynchronously in the first place, isn't it?

    The code you posted doesn't really seem quite right. Since your new Task doesn't await the call to LoadDetails(), that task will finish right away, without any synchronization with the actual work being done. Written differently, you could also avoid having to call through the Dispatcher directly.

    I would have written it something more like this:

    private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
    {
        if (args.Phase != 6)
        {
            throw new Exception("Not in phase 6");
        }
    
        var item = args.Item as ItemModel;
    
        var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
        var textBlock = (TextBlock)templateRoot.FindName("textBlock");
    
        await LoadDetails(textBlock, item.Id);
    }
    
    private async Task LoadDetails(TextBlock textBlock, string id)
    {
        int count = await DataSource.GetItemCounts(id);
    
        textBlock.Text = count.ToString();
    }
    

    I.e. as long as you keep awaiting on the UI thread, you don't need to invoke via the Dispatcher. Note that the above assumes you need the LoadDetails() method, presumably because you call it from multiple places and some require this particular implementation for some reason. But note that you could have just written the LoadItemCounts() method like this, and left out the LoadDetails() method altogether:

    private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
    {
        if (args.Phase != 6)
        {
            throw new Exception("Not in phase 6");
        }
    
        var item = args.Item as ItemModel;
    
        var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
        var textBlock = (TextBlock)templateRoot.FindName("textBlock");
    
        textBlock.Text = (await DataSource.GetItemCounts(id)).ToString();
    }