微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Xamarin iOS WebException:HttpWebRequest完成后,应用程序崩溃

如何解决Xamarin iOS WebException:HttpWebRequest完成后,应用程序崩溃

问题

因此,我们正在创建Xamarin iOS应用,该应用将连接到在ASP .NET上运行的REST API。为了执行HttpRequest,它使用了我们在iOS应用和Android应用之间共享的库。

乍一看,一切正常。 iOS应用会从我们的库中调用异步方法,从服务器接收数据并在表格视图中正确显示。但是由于某种原因,它不会终止连接,因此会在一段时间后WebException(似乎是从执行请求后立即开始直到几分钟后才是随机的)说Error getting response stream (ReadDoneAsync2): ReceiveFailure。有趣的是,当我们从控制台应用程序在库中调用完全相同的方法时,一切正常。

代码

这是我们库中的相关代码(由于主UI线程崩溃,我们不知道错误发生在哪里。调试也没有发现任何可疑的内容。因此,我将在下面包括所有将要执行的代码在API调用期间在客户端上):

ClubProvider:

public class ClubProvider : BaseProvider
{
    /// <summary>
    /// Creates a new <see cref="ClubProvider"/> using the specified <paramref name="uri"/>.
    /// </summary>
    internal ClubProvider(string uri)
    {
        if (!uri.EndsWith("/"))
        {
            uri += "/";
        }
        Uri = uri + "clubs";
    }

    public async Task<ClubListResponse> GetClubListAsync()
    {
        return await ReceiveServiceResponseAsync<ClubListResponse>(Uri);
    }
}

BaseProvider(执行实际HttpWebRequest的地方)

public abstract class BaseProvider
{
    public virtual string Uri { get; private protected set; }

    /// <summary>
    /// Reads the response stream of the <paramref name="webResponse"/> as a UTF-8 string.
    /// </summary>
    private async Task<string> ReadResponseStreamAsync(HttpWebResponse webResponse)
    {
        const int bufferSize = 4096;
        using Stream responseStream = webResponse.GetResponseStream();
        StringBuilder builder = new StringBuilder();
        byte[] buffer = new byte[bufferSize];
        int bytesRead;
        while ((bytesRead = await responseStream.ReadAsync(buffer,bufferSize)) != 0)
        {
            builder.Append(Encoding.UTF8.GetString(buffer,bytesRead));
        }
        return builder.ToString();
    }

    /// <summary>
    /// Performs the <paramref name="webRequest"/> with the provided <paramref name="payload"/> and returns the resulting <see cref="HttpWebResponse"/> object.
    /// </summary>
    private async Task<HttpWebResponse> GetResponseAsync<T>(HttpWebRequest webRequest,T payload)
    {
        webRequest.Host = "ClubmappClient";
        if (payload != null)
        {
            webRequest.ContentType = "application/json";
            string jsonPayload = JsonConvert.SerializeObject(payload);
            ReadOnlyMemory<byte> payloadBuffer = Encoding.UTF8.GetBytes(jsonPayload);
            webRequest.ContentLength = payloadBuffer.Length;
            using Stream requestStream = webRequest.GetRequestStream();
            await requestStream.WriteAsync(payloadBuffer);
        }
        else
        {
            webRequest.ContentLength = 0;
        }
        HttpWebResponse webResponse;
        try
        {
            webResponse = (HttpWebResponse)await webRequest.GetResponseAsync();
        }
        catch (WebException e)
        {
            /* Error handling
            ....
            */
        }
        return webResponse;
    }

    /// <summary>
    /// Performs the <paramref name="webRequest"/> with the provided <paramref name="payload"/> and returns the body of the resulting <see cref="HttpWebResponse"/>.
    /// </summary>
    private async Task<string> GetJsonResponseAsync<T>(HttpWebRequest webRequest,T payload)
    {
        using HttpWebResponse webResponse = await GetResponseAsync(webRequest,payload);
        return await ReadResponseStreamAsync(webResponse);
    }

    /// <summary>
    /// Performs an <see cref="HttpWebRequest"/> with the provided <paramref name="payload"/> to the specified endpoint at the <paramref name="uri"/>. The resulting <see cref="IResponse"/> will be deserialized and returned as <typeparamref name="TResult"/>.
    /// </summary>
    private protected async Task<TResult> ReceiveServiceResponseAsync<TResult,T>(string uri,T payload) where TResult : IResponse
    {
        HttpWebRequest webRequest = WebRequest.CreateHttp(uri);
        webRequest.Method = "POST";
        string json = await GetJsonResponseAsync(webRequest,payload);
        TResult result = JsonConvert.DeserializeObject<TResult>(json);
        return result;
    }

    /// <summary>
    /// Performs an <see cref="HttpWebRequest"/> to the specified endpoint at the <paramref name="uri"/>. The resulting <see cref="IResponse"/> will be deserialized and returned as <typeparamref name="TResult"/>.
    /// </summary>
    private protected async Task<TResult> ReceiveServiceResponseAsync<TResult>(string uri) where TResult : IResponse
    {
        return await ReceiveServiceResponseAsync<TResult,object>(uri,null);
    }
}

从iOS客户端拨打电话

public async override void ViewDidLoad()
{
    // ...
    ServiceProviderFactory serviceProviderFactory = new ServiceProviderFactory("https://10.0.0.40:5004/api");
    ClubProvider clubProvider = serviceProviderFactory.GetClubProvider();
    ClubListResponse clubListResponse = await clubProvider.GetClubListAsync();
    var clublist = new List<ClubProfileListData>();
    foreach (var entry in clubListResponse.ClubListEntries)
    {
        clublist.Add(new ClubProfileListData(entry.Name,entry.City + "," + entry.Country,entry.MusicGenres.Aggregate(new StringBuilder(),(builder,current) => builder.Append(current).Append(",")).ToString()));
    }
    // ...
}

起初,这似乎工作正常,但随后由于以下错误消息而崩溃:

error

通过Wireshark查看执行的请求,发现连接从未终止:

enter image description here

在应用程序崩溃之前,连接保持打开状态,我们关闭了模拟器。 有趣的是,该应用程序并不总是立即崩溃。我们将接收到的数据加载到TableView中,并且偶尔加载数据后应用程序不会崩溃。仅当我们开始滚动结果时它才会崩溃。这对我们来说没有任何意义,因为所有网络流现在都应该关闭了吗? (毕竟,我们对所有using使用ResponseStreams语句。因此,从等待的Task:C返回时,应自动处理所有流。好像它将根据需要尝试流式传输数据。 / p>

使用控制台应用程序测试库代码

现在明显的原因可能是我们忘记了关闭库中的某些流,但是以下代码成功执行,没有任何错误

class Program
{
    static void Main(string[] args)
    {
        new Thread(Test).Start();
        while (true)
        {
            Thread.Sleep(1000);
        }
    }

    public static async void test()
    {
        ServiceProviderFactory serviceProviderFactory = new ServiceProviderFactory("https://10.0.0.40:5004/api");
        ClubProvider clubProvider = serviceProviderFactory.GetClubProvider();
        ClubListResponse clubListResponse = await clubProvider.GetClubListAsync();
        foreach (ClubListResponseEntry entry in clubListResponse.ClubListEntries)
        {
            Console.WriteLine(entry.Name);
        }
        Console.WriteLine("WebRequest complete!");
        Console.ReadLine();
    }
}

看看捕获的数据包,我们发现请求完成后连接已按预期关闭

enter image description here

问题

那为什么呢?为什么我们的库代码可以在.NET Core控制台应用程序中按预期工作,但在iOS应用程序调用时无法断开连接?我们怀疑这可能是由于async/await调用as described here)引起的。但是,我们确实有例外,因此我们不确定这是否与上面链接的问题中所述的错误相同。现在,在重写所有库代码之前,我们希望消除此怪异行为的所有其他可能原因。同样,我们成功地使用了async调用来加载了一些图像而不会导致应用程序崩溃,所以我们现在只是在猜测:C

任何帮助将不胜感激。

解决方法

进一步的测试表明,崩溃的应用实际上与我们的API调用无关。问题实际上是我们按需异步加载要在数据集旁边显示的图像。事实证明,在测试图像加载期间,我们在ListView中只有20个条目。这很好,因为iOS可以一次加载所有图像。但是,当从我们的API加载1500多个数据集时,ListView开始缓冲,仅根据需要加载图像,那时应用程序崩溃了。可能是因为原始图像流不再可用或类似的东西了。

还有一个有趣的注意事项:iOS 确实确实关闭了与服务器的网络连接,但仅在Windows .NET Core控制台应用程序立即关闭它的无数据包超时100秒之后。我们从来没有等那么久。哦:D

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。