如何解决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()));
}
// ...
}
起初,这似乎工作正常,但随后由于以下错误消息而崩溃:
通过Wireshark查看执行的请求,发现连接从未终止:
在应用程序崩溃之前,连接保持打开状态,我们关闭了模拟器。
有趣的是,该应用程序并不总是立即崩溃。我们将接收到的数据加载到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();
}
}
看看捕获的数据包,我们发现请求完成后连接已按预期关闭:
问题
那为什么呢?为什么我们的库代码可以在.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 举报,一经查实,本站将立刻删除。