carbin
carbin

Reputation: 3027

Update UI element from a different thread in Windows 8/WinRT

I have a button and a textblock in a Windows 8 "Metro" application. When the button is clicked it calls a webservice via HttpWebRequest.

private void buttonGo_Click(object sender, RoutedEventArgs e)
{
    HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://localhost/");

    req.BeginGetResponse(ResponseCallback, req);
}

private void ResponseCallback(IAsyncResult asyncResult)
{
    HttpWebRequest req = (HttpWebRequest)asyncResult.AsyncState;

    HttpWebResponse res = (HttpWebResponse)req.EndGetResponse(asyncResult);
    Stream streamResponse = res.GetResponseStream();
    StreamReader streamRead = new StreamReader(streamResponse);
    string responseString = streamRead.ReadToEnd();

    info.Text = responseString; // Can't do this as outside the UI thread
}

I want to update info.Text with the data returned from the WebRequest, however it causes an error: "The application called an interface that was marshalled for a different thread." I understand that this is because it is not being called from the UI thread.

I have found various different solutions involving Dispatcher, SynchronizationContext, the await keyword.

What is the easiest/best way to do this?

Upvotes: 1

Views: 5743

Answers (3)

Jürgen Bayer
Jürgen Bayer

Reputation: 3023

Like Damir said we really should use the async/await pattern but sometimes it's simply necessary to update the UI in a worker thread (task). This is done using the current CoreDispatcher that dispatches the invocation to the UI thread:

private void ResponseCallback(IAsyncResult asyncResult)
{
   ...

   this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
   {
      info.Text = responseString;
   });
}

Upvotes: 4

Damir Arh
Damir Arh

Reputation: 17855

In a Windows Store app you really should use the async await pattern for all asynchronous calls - it is the simplest and most effective way. You can rewrite your existing code to use this pattern as follows:

private async void buttonGo_Click(object sender, RoutedEventArgs e)
{
    HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://localhost/");

    HttpWebResponse res = (HttpWebResponse)(await req.GetResponseAsync());
    Stream streamResponse = res.GetResponseStream();
    StreamReader streamRead = new StreamReader(streamResponse);
    // all IO operations should be called asynchronously
    string responseString = await streamRead.ReadToEndAsync();

    info.Text = responseString; // This way the code runs on UI thread
}

The code after each await keyword is effectively behaving as if it was in a callback but it always executes on the UI thread so that you don't have to worry about it.

All IO bound operations in APIs for Windows Store apps are available in asynchronous flavor. You should prefer them over synchronous ones even when both are available to prevent blocking of UI thread - such as in ReadToEndAsync example above.

Upvotes: 2

Stephen Cleary
Stephen Cleary

Reputation: 456497

Easiest and best:

private async void buttonGo_Click(object sender, RoutedEventArgs e)
{
  using (var client = new HttpClient())
  {
    info.Text = await client.GetStringAsync("http://localhost/");
  }
}

Under the covers, await captures the current SynchronizationContext and resumes the method in that context. The Win8/WinRT SynchronizationContext in turn uses the Dispatcher.

Upvotes: 1

Related Questions