Xamarin 表单 HTTPClient 调用崩溃
Xamarin Form HTTPClient Call crashing
我有一个正在使用 System.Net.Http.HttpClient 的项目。我正在尝试集中对我的 Web API 的所有调用,以便我有常见的错误处理等。我在我的项目中创建了以下 class。
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 class 中创建的实例对象调用函数,如下所示:
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 都在一个静态 class 中,所以我只得到一个实例,如下所示:
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 class 时,它似乎崩溃了。我从来没有从中得到 return 。没有出现异常,我的程序也没有继续。
lobj_HTTPResponse = await lobj_HTTPClient.GetAsync(ps_URI);
如果我从我的 ViewModel 发出我认为完全相同的调用,它就会起作用。 (我在视图模型中调用了 WebAPICaller class 并直接调用了 GetAsync,因此您可以通过评论和取消评论来测试它。)
知道我做错了什么吗?
Link 到完整示例项目:
https://1drv.ms/u/s!Ams6cZUzaeQy3M8uGAuaGggMt0Fi-A
我看到了一些正在发生的事情。一、commented-out代码:
//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() 中执行此操作是否是个好主意。这可能会导致您的应用看起来 non-responsive.
Device.BeginInvokeOnMainThread 的一般用途是当你不知道你当前是否在主线程上时,但是这些 UI 事件处理程序总是在主线程上被调用Xamarin 平台。
这就是我的发现。似乎等待 HTTPClient.GetAsync 导致了错误。 (很确定线程被阻塞了。)所以首先我取出等待并添加代码以在 HTTPClient 的 return 任务未完成时延迟任务。
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");
}
}
我可以从出现的事件中删除异步并简单地同步调用 loaddata 事件。
if (!ViewModelObjects.Languages.IsDataLoaded)
ViewModelObjects.Languages.LoadData();
进行所有这些更改后,达到了预期的结果。一切都以同步方式 运行(我知道对 HTTPClient 的 GetAsync 函数的调用仍然是异步的)并且在结果被检索并加载到对象中之前不会 return。
我希望这可以帮助其他人。 :)
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
方法在事件触发之前被调用。
订阅 page/view
的 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;
}
这样在等待任务完成时就不需要忙等待延迟线程了。
我有一个正在使用 System.Net.Http.HttpClient 的项目。我正在尝试集中对我的 Web API 的所有调用,以便我有常见的错误处理等。我在我的项目中创建了以下 class。
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 class 中创建的实例对象调用函数,如下所示:
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 都在一个静态 class 中,所以我只得到一个实例,如下所示:
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 class 时,它似乎崩溃了。我从来没有从中得到 return 。没有出现异常,我的程序也没有继续。
lobj_HTTPResponse = await lobj_HTTPClient.GetAsync(ps_URI);
如果我从我的 ViewModel 发出我认为完全相同的调用,它就会起作用。 (我在视图模型中调用了 WebAPICaller class 并直接调用了 GetAsync,因此您可以通过评论和取消评论来测试它。)
知道我做错了什么吗?
Link 到完整示例项目: https://1drv.ms/u/s!Ams6cZUzaeQy3M8uGAuaGggMt0Fi-A
我看到了一些正在发生的事情。一、commented-out代码:
//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() 中执行此操作是否是个好主意。这可能会导致您的应用看起来 non-responsive.
Device.BeginInvokeOnMainThread 的一般用途是当你不知道你当前是否在主线程上时,但是这些 UI 事件处理程序总是在主线程上被调用Xamarin 平台。
这就是我的发现。似乎等待 HTTPClient.GetAsync 导致了错误。 (很确定线程被阻塞了。)所以首先我取出等待并添加代码以在 HTTPClient 的 return 任务未完成时延迟任务。
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");
}
}
我可以从出现的事件中删除异步并简单地同步调用 loaddata 事件。
if (!ViewModelObjects.Languages.IsDataLoaded)
ViewModelObjects.Languages.LoadData();
进行所有这些更改后,达到了预期的结果。一切都以同步方式 运行(我知道对 HTTPClient 的 GetAsync 函数的调用仍然是异步的)并且在结果被检索并加载到对象中之前不会 return。
我希望这可以帮助其他人。 :)
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
方法在事件触发之前被调用。
订阅 page/view
的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;
}
这样在等待任务完成时就不需要忙等待延迟线程了。