WinRT - data loading while saving the user interface

I am developing a Windows Metro application and I am having a problem with the user interface becoming unresponsive. As far as I can tell, the reason is this:

<ListView ... SelectionChanged="ItemListView_SelectionChanged" ... 

This event is handled here:

  async void ItemListView_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (this.UsingLogicalPageNavigation()) this.InvalidateVisualState(); MyDataItem dataItem = e.AddedItems[0] as MyDataItem; await LoadMyPage(dataItem); } private async Task LoadMyPage(MyDataItem dataItem) { SyndicationClient client = new SyndicationClient(); SyndicationFeed feed = await client.RetrieveFeedAsync(new Uri(FEED_URI)); string html = ConvertRSSToHtml(feed) myWebView.NavigateToString(html, true); } 

LoadMyPage takes time to complete because it receives data from the web service and loads it onto the screen. However, the user interface seems to be waiting for him: I think, until the above event completes.

So my question is: what can I do about this? Is there a better event that I can connect to, or is there another way to handle this? I thought about starting a background job, but for me it seems like an overkill.

EDIT:

To clarify the scope of this problem, I am talking about a maximum of 3-4 seconds that do not respond. This is by no means lengthy work.

EDIT:

I tried some suggestion below, however the whole call stack from SelectionChanged function uses async / await. I tracked this statement:

 myFeed = await client.RetrieveFeedAsync(uri); 

Which does not seem to continue processing until its completion.

EDIT:

I understand that this is turning into "War and Peace," but below is a replication of the problem using an empty metro application and button:

XAML:

 <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <StackPanel> <Button Click="Button_Click_1" Width="200" Height="200">test</Button> <TextBlock x:Name="test"/> </StackPanel> </Grid> 

Code behind:

  private async void Button_Click_1(object sender, RoutedEventArgs e) { SyndicationFeed feed = null; SyndicationClient client = new SyndicationClient(); Uri feedUri = new Uri(myUri); try { feed = await client.RetrieveFeedAsync(feedUri); foreach (var item in feed.Items) { test.Text += item.Summary.Text + Environment.NewLine; } } catch { test.Text += "Connection failed\n"; } } 
+6
source share
5 answers

Try to try ...

 SyndicationFeed feed = null; SyndicationClient client = new SyndicationClient(); var feedUri = new Uri(myUri); try { var task = client.RetrieveFeedAsync(feedUri).AsTask(); task.ContinueWith((x) => { var result = x.Result; Parallel.ForEach(result.Items, item => { Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { test.Text += item.Title.Text; }); }); }); } catch (Exception ex) { } 

I tried this on my machine, adding a button to the application using the Grid application template. I could scroll the grid of items back and forth until I updated the page title without any problems. Although I did not have many items that went very fast, it was difficult to be 100% positive.

+5
source

Since you are using await before LoadMyPage , I assume that it compiles and returns << 22>. Given this, I created a small example.

Suppose LoadMyPage (and Sleep() ) looks like this:

 public Task<string> LoadMyPage() { return Task<string>.Factory.StartNew(() => { Sleep(3000); return "Hello world"; }); } static void Sleep(int ms) { new ManualResetEvent(false).WaitOne(ms); } 

And what XAML looks like:

 <StackPanel> <TextBlock x:Name="Result" /> <ListView x:Name="MyList" SelectionChanged="ItemListView_SelectionChanged"> <ListViewItem>Test</ListViewItem> <ListViewItem>Test2</ListViewItem> </ListView> <Button>Some Button</Button> <Button>Some Button2</Button> </StackPanel> 

Then we can handle the SelectionChanged event as follows:

 private async void ItemListView_SelectionChanged(object sender, SelectionChangedEventArgs e) { MyList.IsEnabled = false; var result = await LoadMyPage(); Result.Text = result; MyList.IsEnabled = true; } 

The Task return returned by LoadMyPage will be executed in parallel, which means that the UI should not freeze during this task. Now, to get the result from this Task , you use await . This will create a continuation block.

So, in this example, when you select something, the ListView turned off for the entire load time and then turned on again after the Task completes. You can verify that the user interface did not freeze by clicking buttons to see that it is still responding.

If LoadMyPage interacts with the user interface, you need to rebuild it a bit, return it to the ViewModel or the result you want, and then put everything together in the user interface stream again.

+4
source

The background thread is definitely not crowded. This is how you deal with such a problem.

Do not perform lengthy tasks in the user interface thread, otherwise you will bind the user interface and cause it to stop responding. Run them in the background thread, and then create this thread that can be processed by the main user interface thread when it finishes.

Displaying a progress indicator in the user interface thread is also useful. Users like to know that something is happening. This will assure them that the application is not broken or frozen, and they will be ready to wait a little longer. This is why all web browsers have some kind of β€œtrial” or other download indicator.

+2
source

The most likely problem is that LoadMyPage is doing something synchronously. Remember, async does not run your code in the background thread; by default, all of its actual code will be run in the user interface thread (see asynchronous / waiting FAQ or my async / wait intro ). So, if you block the asynchronous method, it still blocks the calling thread.

Take a look at LoadMyPage . Is await to call a web service? Does it do expensive data processing before putting it into the user interface? Is this an overwhelming UI (many Windows controls have scalability issues when they get to thousands of items)?

+2
source

Looking at your simplified code example, I believe that your problem is everything that is out of the waiting line.

In the following block of code:

 private async void Button_Click_1(object sender, RoutedEventArgs e) { SyndicationFeed feed = null; SyndicationClient client = new SyndicationClient(); Uri feedUri = new Uri(myUri); try { feed = await client.RetrieveFeedAsync(feedUri); foreach (var item in feed.Items) { test.Text += item.Summary.Text + Environment.NewLine; } } catch { test.Text += "Connection failed\n"; } } 

The only line running in the background thread is the line

 feed = await client.RetrieveFeedAsync(feedUri); 

All other lines of code in this block are executed in the user interface thread.

Just because your button handler is marked as async does not mean that the code that does not work in the user interface thread does not work inside it. In fact, the event handler runs in the user interface thread. Thus, the creation of SyndicationClient and the installation of Uri occurs in the user interface thread.

Something that many developers do not understand is that any code that comes after waiting will automatically resume in the same thread that was used before waiting. This means that the code

  foreach (var item in feed.Items) { test.Text += item.Summary.Text + Environment.NewLine; } 

works in user interface thread!

This is convenient in that you do not need to do Dispatcher.Invoke to update test.Text , but it also means that you block the UI thread all the time when you loop elements and string concatenations.

In your (albeit simplified) example, the easiest way to do this work in the background thread would be to have another method in SyndicationClient called RetrieveFeedAsStringAsync ; SyndicationClient can then do the loading, looping and concatenation of strings as part of its own task. Upon completion of this task, the only line of code that will run in the user interface thread will assign the TextBox text.

+2
source

Source: https://habr.com/ru/post/922445/


All Articles