2

我有一個項目,我使用System.Net.Http.HttpClient。我正試圖集中所有對我的Web API的調用,以便處理常見錯誤等。我在我的項目中創建了以下類。Xamarin表單HTTPClient調用崩潰

using ModernHttpClient; 
using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Net.Http; 
using System.Net.Http.Headers; 
using System.Text; 
using System.Threading.Tasks; 

namespace WebAPIHelper 
{ 
    class WebAPICaller 
    { 
     public async Task<string> CallWebService(string ps_URI) 
     { 
      HttpClient lobj_HTTPClient = null; 
      HttpResponseMessage lobj_HTTPResponse = null; 
      string ls_Response = ""; 

      //We assume the internet is available. 
      try 
      { 
       //Get the Days of the Week 
       lobj_HTTPClient = new HttpClient(new NativeMessageHandler()); 
       lobj_HTTPClient.BaseAddress = new Uri(App.APIPrefix); 
       lobj_HTTPClient.DefaultRequestHeaders.Accept.Clear(); 
       lobj_HTTPClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 

       lobj_HTTPResponse = await lobj_HTTPClient.GetAsync(ps_URI); 

       if (!lobj_HTTPResponse.IsSuccessStatusCode) 
       { 
        Debug.WriteLine(lobj_HTTPResponse.ReasonPhrase); 
       } 
       else 
       { 
        ls_Response = await lobj_HTTPResponse.Content.ReadAsStringAsync(); 
       } 
      } 
      catch (Exception ex) 
      { 
       Debug.WriteLine(ex.Message); 
      } 
      finally 
      { 
       if (lobj_HTTPClient != null) 
        lobj_HTTPClient.Dispose(); 
       if (lobj_HTTPResponse != null) 
       { 
        lobj_HTTPResponse.Dispose(); 
       } 
      } 

      return ls_Response; 

     } 

    } 
} 

我打電話從我在ViewModel類創建學習語言的實例對象的功能如下:

using ModernHttpClient; 
using Newtonsoft.Json; 
using System; 
using System.Collections.ObjectModel; 
using System.ComponentModel; 
using System.Diagnostics; 
using System.Net.Http; 
using System.Net.Http.Headers; 
using System.Runtime.CompilerServices; 
using System.Threading.Tasks; 

namespace WebAPIHelper 
{ 
    public class VM_Languages : INotifyPropertyChanged 
    { 
     /// <summary> 
     /// A collection for CGSLanguage objects. 
     /// </summary> 
     public ObservableCollection<GBSLanguage_ForList> Items_ForList { get; private set; } 
     const string ic_LanguagesAPIUrl = @"/languages/true"; 

     /// <summary> 
     /// Constructor for the Languages view model. 
     /// </summary> 
     public VM_Languages() 
     { 
      this.Items_ForList = new ObservableCollection<GBSLanguage_ForList>(); 
     } 

     /// <summary> 
     /// Indicates of the view model data has been loaded 
     /// </summary> 
     public bool IsDataLoaded 
     { 
      get; 
      private set; 
     } 

     /// <summary> 
     /// Creates and adds a the countries to the collection. 
     /// </summary> 
     public async Task LoadData() 
     { 
      HttpClient lobj_HTTPClient = null; 
      HttpResponseMessage lobj_HTTPResponse = null; 
      string ls_Response = ""; 

      try 
      { 
       IsDataLoaded = false; 
       string ls_WorkLanguageURI = ic_LanguagesAPIUrl; 

       //Get the Days of the Week 
       lobj_HTTPClient = new HttpClient(new NativeMessageHandler()); 
       lobj_HTTPClient.BaseAddress = new Uri(App.APIPrefix); 
       lobj_HTTPClient.DefaultRequestHeaders.Accept.Clear(); 
       lobj_HTTPClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 

       **//This call will not work 
       //WebAPICaller lobj_APICaller = new WebAPICaller(); 
       //ls_Response = lobj_APICaller.CallWebService(ls_WorkLanguageURI).Result; 

       //This call will work 
       lobj_HTTPResponse = await lobj_HTTPClient.GetAsync(ls_WorkLanguageURI);** 


       if (lobj_HTTPResponse.IsSuccessStatusCode) 
       { 

        if (this.Items_ForList != null) 
         this.Items_ForList.Clear(); 

        ls_Response = await lobj_HTTPResponse.Content.ReadAsStringAsync(); 
        Items_ForList = JsonConvert.DeserializeObject<ObservableCollection<GBSLanguage_ForList>>(ls_Response); 

       } 
      } 
      catch (Exception ex) 
      { 
       Debug.WriteLine(ex.Message); 
      } 
      finally 
      { 
       this.IsDataLoaded = true; 
       NotifyPropertyChanged("GBSLanguages_ForList"); 
      } 
     } 

     /// <summary> 
     /// Notifies subscribers that a property has changed. 
     /// </summary> 
     public event PropertyChangedEventHandler PropertyChanged; 
     private void NotifyPropertyChanged(String propertyName) 
     { 
      PropertyChangedEventHandler handler = PropertyChanged; 
      if (null != handler) 
      { 
       handler(this, new PropertyChangedEventArgs(propertyName)); 
      } 
     } 

     protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 
     { 
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 
     } 
    } 
} 

我有一個靜態類所有的ViewModels所以我只得到一個實例它們如下:

namespace WebAPIHelper 
{ 
    public static class ViewModelObjects 
    { 
     private static VM_Languages iobj_Languages; 

     public static VM_Languages Languages 
     { 
      get 
      { 
       if (iobj_Languages == null) 
        iobj_Languages = new VM_Languages(); 
       return iobj_Languages; 
      } 

     } 
    } 
} 

在我的出現在我的主網頁的代碼,我有以下調用從的WebAPI檢索數據

protected override async void OnAppearing() 
    { 
     Device.BeginInvokeOnMainThread(() => 
     { 
      if (!ViewModelObjects.Languages.IsDataLoaded) 
       ViewModelObjects.Languages.LoadData(); 

     }); 

     //If the DOW and Language data are not loaded yet - wait 
     while (!ViewModelObjects.Languages.IsDataLoaded) 
     { 
      await Task.Delay(1000); 
     } 

    } 

問題是當我嘗試使用我的WebAPICaller類時,它似乎崩潰了。我從來沒有從它得到回報。從來沒有例外,我的程序永遠不會繼續。

lobj_HTTPResponse = await lobj_HTTPClient.GetAsync(ps_URI); 

如果我讓我相信是從我的ViewModel完全相同的調用,它的作品。 (我既有對WebAPICaller類的調用,也有對視圖模型中的GetAsync的直接調用,因此您可以通過評論和取消註釋來對其進行測試。)

任何有關我在做什麼錯誤的想法?

鏈接到全樣本項目: https://1drv.ms/u/s!Ams6cZUzaeQy3M8uGAuaGggMt0Fi-A

+2

使用Fiddler或類似的工具來查看正在對API做出什麼請求。這聽起來更像是一個調試問題... – CodingYoshi

+0

這是因爲你正在混合異步和阻止調用。除非'OnAppearing'是一個事件處理程序,否則該方法中的異步調用將會死鎖。 – Nkosi

+0

出現時實際上是頁面中的事件處理程序。你所描述的是我想到的,但不知道如何修復它 –

回答

1

所以這裏是我發現的。看來等待HTTPClient.GetAsync導致錯誤。 (很確定線程被阻塞了。)因此,首先我拿出了await並添加了代碼,以便在HTTPClient的返回任務未完成時延遲任務。

var lobj_Result = lobj_HTTPClient.GetAsync(ps_URI); 

while (!lobj_Result.IsCompleted) 
{ 
    Task.Delay(100); 
} 

因爲我不再等待在LoadData方法的調用,我能夠刪除異步任務聲明,只是讓它的方法。

public void LoadData() 
    { 
     HttpClient lobj_HTTPClient = null; 
     HttpResponseMessage lobj_HTTPResponse = null; 
     string ls_Response = ""; 

     try 
     { 
      IsDataLoaded = false; 
      string ls_WorkLanguageURI = ic_LanguagesAPIUrl; 

      //Get the Days of the Week 
      lobj_HTTPClient = new HttpClient(new NativeMessageHandler()); 
      lobj_HTTPClient.BaseAddress = new Uri(App.APIPrefix); 
      lobj_HTTPClient.DefaultRequestHeaders.Accept.Clear(); 
      lobj_HTTPClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 

      //This call will not work 
      WebAPICaller lobj_APICaller = new WebAPICaller(); 
      ls_Response = lobj_APICaller.CallWebService(ls_WorkLanguageURI).Result; 

      if (ls_Response.Trim().Length > 0) 
      { 
       if (this.Items_ForList != null) 
        this.Items_ForList.Clear(); 
       Items_ForList = JsonConvert.DeserializeObject<ObservableCollection<GBSLanguage_ForList>>(ls_Response); 

      } 
     } 
     catch (Exception ex) 
     { 
      Debug.WriteLine(ex.Message); 
     } 
     finally 
     { 
      this.IsDataLoaded = true; 
      NotifyPropertyChanged("GBSLanguages_ForList"); 
     } 
    } 

我可以從出現的事件中刪除異步,並簡單地同步調用loaddata事件。

if (!ViewModelObjects.Languages.IsDataLoaded) 
       ViewModelObjects.Languages.LoadData(); 

完成所有這些更改後,達到了預期效果。所有東西都以同步方式運行(我知道調用HTTPClient的GetAsync函數仍然是異步的),並且直到結果被檢索並加載到對象中才會返回。

我希望這可以幫助別人。 :)基礎上source code on github

void IPageController.SendAppearing() 
{ 
    if (_hasAppeared) 
     return; 

    _hasAppeared = true; 

    if (IsBusy) 
     MessagingCenter.Send(this, BusySetSignalName, true); 

    OnAppearing(); 
    EventHandler handler = Appearing; 
    if (handler != null) 
     handler(this, EventArgs.Empty); 

    var pageContainer = this as IPageContainer<Page>; 
    ((IPageController)pageContainer?.CurrentPage)?.SendAppearing(); 
} 

+0

看看我的方法,看看這是否適合你。 – Nkosi

+0

我的問題是AsyncTasks的連續午餐。所以我把我在下一個任務的結束和開始之間延遲了200ms。這解決了它。 – IgniteCoders

0

有一對夫婦的事情,我看怎麼回事。首先,註釋代碼:

  //This call will not work 
      //WebAPICaller lobj_APICaller = new WebAPICaller(); 
      //ls_Response = lobj_APICaller.CallWebService(ls_WorkLanguageURI).Result; 

正在使用.Result而不是await。這可能是你的問題的根源。一般應避免使用.Result,只需使用await即可。有關更多詳情,請參閱Await on a completed task same as task.Result?的答案。

第二件事是當OnAppearing被調用時你已經在UI線程上,所以不需要調用Device.BeginInvokeOnMainThread。你在做什麼在目前這個方法等同於:

protected override async void OnAppearing() 
{ 
    if (!ViewModelObjects.Languages.IsDataLoaded) 
     await ViewModelObjects.Languages.LoadData(); 
} 

接下來的問題是,是否這是一個偉大的想法在OnAppearing在做這個()。這可能會導致您的應用看起來不響應。

Device.BeginInvokeOnMainThread的一般用途是用於不知道當前是否在主線程中,但這些UI事件處理程序總是在Xamarin平臺的主線程上調用。

+0

。結果不是問題,因爲我從來沒有去過那行代碼。等待和另一個等待似乎是挑戰。我想我已經修好了,稍後再測試一下,我會在幾分鐘後發佈一個答案。 –

0

還有一種方式與異步做到這一點/等待的方法。

您會注意到在觸發事件之前調用了OnAppearing方法。

訂閱Appearing事件的頁面/視圖

protected override void OnAppearing() { 
    this.Appearing += Page_Appearing; 
} 

和像你這樣原本創建一個異步方法,但這次有它實際的,甚至處理

private async void Page_Appearing(object sender, EventArgs e) { 
    if (!ViewModelObjects.Languages.IsDataLoaded) 
     await ViewModelObjects.Languages.LoadData(); 
    //unsubscribing from the event 
    this.Appearing -= Page_Appearing; 
} 

有這樣無需等待任務完成時等待延遲線程。