【问题标题】:Xamarin Form HTTPClient Call crashingXamarin Form HTTPClient 调用崩溃
【发布时间】:2017-01-22 21:11:08
【问题描述】:

我有一个使用 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 类的调用,也有对 View Model 中 GetAsync 的直接调用,因此您可以通过注释和取消注释对其进行测试。)

知道我做错了什么吗?

完整示例项目的链接: https://1drv.ms/u/s!Ams6cZUzaeQy3M8uGAuaGggMt0Fi-A

【问题讨论】:

  • 使用 Fiddler 或类似工具查看对 API 发出的请求。这听起来更像是一个调试问题...
  • 这是因为你混合了异步和阻塞调用。除非OnAppearing 是事件处理程序,否则该方法中的异步调用将死锁。
  • 出现实际上是来自页面的事件处理程序。你所描述的是我的想法,但不知道如何解决它
  • @GeorgeMCeaserJr, OnAppearing 不是事件处理程序。它是在Page.Appearing 事件的实际事件处理程序中调用的受保护方法。
  • @GeorgeMCeaserJr,如果你看一下source code on github,你就会明白我指的是什么。

标签: c# async-await xamarin.forms asp.net-web-api2 dotnet-httpclient


【解决方案1】:

这就是我发现的。似乎等待 HTTPClient.GetAsync 导致了错误。 (很确定线程被阻塞了。)所以首先我拿出了等待并添加了代码来延迟 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 函数的调用仍然是异步的)并且直到结果被检索并加载到对象中才返回。

我希望这可以帮助其他人。 :)

【讨论】:

  • 看看我的方法,看看这是否适合你。
  • 我的问题是 AsyncTasks 的连续午餐。所以我在下一个任务的结束和开始之间延迟了 200 毫秒。这样就解决了。
【解决方案2】:

我看到有几件事正在发生。一、注释掉的代码:

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

正在使用 .Result 而不是等待。这可能是您问题的根源。一般应避免使用 .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 平台在主线程上调用。

【讨论】:

  • .Result 不是问题,因为我什至从未接触过那行代码。 Await 和另一个 Await 似乎是一个挑战。我想我已经修复了它,并会在我再测试一下后在几分钟内发布答案。
【解决方案3】:

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

仍然可以使用 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;
}

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

【讨论】:

    猜你喜欢
    • 2021-10-01
    • 2020-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-06
    • 1970-01-01
    相关资源
    最近更新 更多