Xamarin Form HTTPClient调用崩溃



我有一个项目,我正在使用System.Net.Http.HttpClient。我正在尝试集中对我的Web API的所有调用,以便我有常见的错误处理等。我在项目中创建了以下类。

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

我把所有的ViewModel都放在一个静态类中,所以我只得到它们的一个实例,如下所示:

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

下面是我的发现。似乎是等待HTTPClient.GetAsync导致了该错误。(很确定线程被阻塞了。)所以首先,我去掉了等待,并添加了代码,以在HTTPClient的返回任务没有完成时延迟任务。

var lobj_Result = lobj_HTTPClient.GetAsync(ps_URI);
while (!lobj_Result.IsCompleted)
{
Task.Delay(100);
}

因为我不再等待LoadData方法中的调用,所以我可以删除async Task声明,并将其作为一个方法。

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

I可以从出现的事件中删除async,并简单地同步调用loaddata事件。

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

在做出所有这些改变之后,达到了预期的结果。一切都以同步方式运行(我知道HTTPClient的GetAsync函数调用仍然是异步的),直到检索结果并加载到对象中才返回。

我希望这能帮助其他人

我看到有几件事正在发生。首先,注释掉的代码:

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

正在使用.Result而不是等待。这可能是你问题的根源。一般情况下应避免使用.Result,只需使用wait即可。请在此处查看答案等待与任务相同的已完成任务。后果有关原因的更多详细信息。

第二件事是,当调用OnAppearing时,你已经在UI线程上了,所以没有必要调用Device.BeginInvokeOnMainThread。你目前在该方法中所做的相当于:

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

下一个问题是在OnAppearing()中这样做是否是个好主意。这可能会导致你的应用程序看起来没有响应。

Device.BeginInvokeOnMainThread的一般用途是当你不知道自己当前是否在主线程上时,但这些UI事件处理程序总是由Xamarin平台在主线程中调用。

基于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();
}

使用async/await方法仍然有一种方法可以做到这一点。

您会注意到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;
}

这样,就不需要在等待任务完成时忙于等待延迟线程。

最新更新