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; 
} 

有这样无需等待任务完成时等待延迟线程。