c# - Xamarin iOS WebException:应用程序在 HttpWebRequest 完成后崩溃
问题描述
问题
因此,我们正在创建一个 Xamarin iOS 应用程序,它将连接到在 ASP .NET 上运行的 REST API。为了执行 HttpRequests,它使用我们在 iOS 应用程序和 android 应用程序之间共享的库。
乍一看,一切正常。iOS 应用程序从我们的库中调用一个异步方法,从服务器接收数据并在表格视图中正确显示它。但是由于某种原因,一旦完成连接,它就不会终止连接,导致WebException
一段时间后(从请求执行后立即到几分钟后似乎是随机的)说Error getting response stream (ReadDoneAsync2): ReceiveFailure
。有趣的是,当我们从控制台应用程序调用库中完全相同的方法时,一切正常。
图书馆代码
这是我们库中的相关代码(我们不知道错误发生在哪里,因为主 UI 线程崩溃了。调试也没有发现任何可疑之处。因此,我将在下面包含我们将在客户端上执行的所有代码在 API 调用期间):
俱乐部提供者:
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, 0, bufferSize)) != 0)
{
builder.Append(Encoding.UTF8.GetString(buffer, 0, 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()));
}
// ...
}
起初这似乎工作得很好,但后来它崩溃了这个错误消息:
用 Wireshark 查看执行的请求会发现连接永远不会终止:
连接保持打开状态,直到应用程序崩溃并且我们关闭模拟器。有趣的是,该应用程序并不总是立即崩溃。我们将接收到的数据加载到 TableView 中,并且每隔一段时间,应用程序在加载数据后不会崩溃。只有当我们开始滚动结果时它才会崩溃。这对我们来说没有意义,因为所有网络流现在都应该关闭,对吧?(毕竟我们使用using
了 all 的语句ResponseStreams
。因此,当从等待的 Task :C 返回时,所有流都应该自动处理)好像它会根据需要尝试流式传输数据。
使用控制台应用程序测试库代码
现在造成这种情况的明显原因可能是我们忘记关闭库中的某些流,但是以下代码成功且没有任何错误:
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();
}
}
查看捕获的数据包,我们看到请求完成后连接按预期关闭:
问题
那么这是为什么呢?为什么我们的库代码在 .NET Core 控制台应用程序中按预期工作,但在被 iOS 应用程序调用时无法断开连接?我们怀疑这可能是由于async/await
调用造成的(如此处所述)。但是我们确实得到了一个例外,所以我们不确定这是否真的与上面链接的问题中描述的错误相同。现在,在我们重写所有库代码之前,我们希望消除所有其他可能导致这种奇怪行为的原因。此外,我们成功地使用async
调用来加载一些图像而不会导致应用程序崩溃,所以我们现在只是在猜测:C
任何帮助将不胜感激。
解决方案
好吧,进一步的测试表明,应用程序崩溃实际上与我们的 API 调用没有任何关系。问题实际上是我们按需异步加载要在数据集旁边显示的图像。事实证明,在测试图像加载期间,我们的 ListView 中只有 20 个条目。这很好,因为 iOS 可以一次加载所有图像。然而,当从我们的 API 加载 1500 多个数据集时,ListView 开始缓冲,仅根据需要加载图像,这就是应用程序崩溃的时候。可能是因为原始图像流不再可用或类似的东西。
另外一个有趣的旁注:iOS确实关闭了与服务器的网络连接,但仅在 Windows .NET Core 控制台应用程序立即关闭它时没有发送数据包的 100 秒超时之后。我们从来没有等过这么久。哦好吧 :D
推荐阅读
- xamarin - Xamarin Android 不再打印 MONO GC 日志消息
- python - 从特定基础开始从 JSON 集合中获取价值?
- powershell - SCOM - 将 Powershell MP 用于自己的脚本
- c++ - C ++在具有位置细节的字符串中查找不重叠的子字符串
- sql - 复合求和:我想创建一个复合查询,它从两个不同的表中获取两列的单独总和,然后对它们求和
- javascript - Codecademy 中的石头剪刀布游戏中的函数“未定义”消息
- reactjs - 尝试从导航器显示坐标时出现“意外使用'位置'”
- python - Python催化剂数据类型冲突
- r - 如何在 R 中更改数据帧变量的编码
- python - 使用循环生成多个子图