2012-11-29 104 views
1

我在Windows 8「Metro」應用程序中有一個按鈕和一個文本塊。當按鈕被點擊時,它會通過HttpWebRequest調用Web服務。從Windows 8/WinRT中的不同線程更新UI元素

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 
} 

我想更新info.Text從WebRequest的返回的數據,但它會導致一個錯誤:「應用程序調用這是編組爲一個不同的線程的接口。」我知道這是因爲它沒有從UI線程調用。

我發現了各種不同的解決方案,涉及Dispatcher,SynchronizationContext,await關鍵字。

什麼是最簡單/最好的方法來做到這一點?

+1

我沒有使用WinRT的特別,但我想它會像'info.Dispatcher.Invoke(()=>信息.Text = responseString);' –

回答

2

在Windows應用商店應用程序中,您確實應該對所有異步調用使用異步等待模式 - 這是最簡單和最有效的方式。你可以重寫現有的代碼使用此模式如下:

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 
} 

後每個代碼等待關鍵字被有效地行爲,就好像它是一個回調,但讓你不總是執行UI線程不得不擔心它。

Windows應用商店應用程序的API中的所有IO綁定操作都以異步風格提供。即使兩者都可用於防止阻塞UI線程,您應該更喜歡它們,例如上面的ReadToEndAsync示例。

1

最簡單和最:

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

在幕後,await捕獲當前SynchronizationContext並恢復在這方面的方法。 Win8/WinRT SynchronizationContext反過來使用Dispatcher

+0

如果我特別想從ResponseCallback中設置info.Text,我該怎麼做? – zi3guw

+0

那麼,你可以使用'TaskCompletionSource'來將整個回調包裝在一個'Task '中,然後像我貼的那樣使用代碼。或者你可以捕獲'SynchronizationContext'並手動完成你的線程編組。不知道爲什麼你會這樣做,儘管如此。 –

+0

一個原因是,如果你使用.net 4.0,並希望你的異步提供進度。如果使用.net 4.5和後者,他們有IProgress <>或Progress <>,所以如果我理解正確,你可以使用TaskCompletionSource。在.net 4.0中,你運氣不好,所以你需要使用SynchronizationContext將進度傳遞給調用者? – Wes

4

像Damir一樣,我們確實應該使用async/await模式,但有時只需要更新工作線程(任務)中的UI。這是使用電流CoreDispatcher調度該調用到UI線程中完成的:

private void ResponseCallback(IAsyncResult asyncResult) 
{ 
    ... 

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