MonoTouch - WebRequest 内存泄漏和崩溃? [英] MonoTouch - WebRequest memory leak and crash?
问题描述
我有一个使用 3.5MB 文件执行 HTTP POST 的 MonoTouch 应用程序,它在我测试的主要平台(iPhone 3G 操作系统 3.1.2 和 iPhone 4 操作系统 4.2.1)上非常不稳定).我会描述我在这里做什么,如果我做错了什么,也许有人可以告诉我.
I've got a MonoTouch app that does an HTTP POST with a 3.5MB file, and it is very unstable on the primary platforms that I test on (iPhone 3G with OS 3.1.2 and iPhone 4 with OS 4.2.1). I'll describe what I'm doing here and maybe someone can tell me if I'm doing something wrong.
为了排除我应用程序的其余部分,我将其缩减为一个很小的示例应用程序.该应用程序是一个 iPhone OpenGL 项目,它只执行以下操作:
In order to rule out the rest of my app, I've whittled this down to a tiny sample app. The app is an iPhone OpenGL Project and it does only this:
- 在启动时,以 30k 块分配 6MB 内存.这模拟了我的应用程序的内存使用情况.
- 将 3.5MB 的文件读入内存.
- 创建一个线程来发布数据.(制作一个 WebRequest 对象,使用 GetRequestStream(),并写入 3.5MB 的数据).
- 当主线程检测到发帖线程完成时,转到第 2 步并重复.
此外,每一帧,我分配 0-100k 来模拟应用程序做某事.我没有保留对这些数据的任何引用,因此它应该被垃圾收集.
Also, each frame, I allocate 0-100k to simulate the app doing something. I don't keep any references to this data so it should be getting garbage collected.
iPhone 3G 结果:该应用程序通过 6 到 8 次上传,然后操作系统将其杀死.没有崩溃日志,但有一个 LowMemory 日志显示该应用程序已被抛弃.
iPhone 3G Result: The app gets through 6 to 8 uploads and then the OS kills it. There is no crash log, but there is a LowMemory log showing that the app was jettisoned.
iPhone 4 结果:在第 11 次上传时出现 Mprotect 错误.
iPhone 4 Result: It gets an Mprotect error around the 11th upload.
一些数据点:
- 仪器不会显示内存随着应用继续上传而增加.
- 仪器没有显示任何重大泄漏(总共可能 1 KB).
- 无论我是将帖子数据写入 64k 块还是通过一次 Stream.Write() 调用一次性写入都无关紧要.
- 在开始下一次上传之前是否等待响应 (HttpWebRequest.HaveResponse) 并不重要.
- POST 数据是否有效并不重要.我尝试使用有效的 POST 数据,并且尝试发送 3MB 的零.
- 如果应用程序没有在每一帧分配任何数据,那么内存不足需要更长的时间(但如前所述,我为每一帧分配的内存在分配它的帧之后没有被引用,所以它应该由 GC 收集).
如果没有人有任何想法,我会向 Novell 提交错误,但我想先看看我是否在这里做错了什么.
If nobody has any ideas, I'll file a bug with Novell, but I wanted to see if I'm doing something wrong here first.
如果有人想要完整的示例应用程序,我可以提供,但我已将 EAGLView.cs 的内容粘贴在下面.
If anyone wants the full sample app, I can provide it, but I've pasted the contents of my EAGLView.cs below.
using System;
using System.Net;
using System.Threading;
using System.Collections.Generic;
using System.IO;
using OpenTK.Platform.iPhoneOS;
using MonoTouch.CoreAnimation;
using OpenTK;
using OpenTK.Graphics.ES11;
using MonoTouch.Foundation;
using MonoTouch.ObjCRuntime;
using MonoTouch.OpenGLES;
namespace CrashTest
{
public partial class EAGLView : iPhoneOSGameView
{
[Export("layerClass")]
static Class LayerClass ()
{
return iPhoneOSGameView.GetLayerClass ();
}
[Export("initWithCoder:")]
public EAGLView (NSCoder coder) : base(coder)
{
LayerRetainsBacking = false;
LayerColorFormat = EAGLColorFormat.RGBA8;
ContextRenderingApi = EAGLRenderingAPI.OpenGLES1;
}
protected override void ConfigureLayer (CAEAGLLayer eaglLayer)
{
eaglLayer.Opaque = true;
}
protected override void OnRenderFrame (FrameEventArgs e)
{
SimulateAppAllocations();
UpdatePost();
base.OnRenderFrame (e);
float[] squareVertices = { -0.5f, -0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f };
byte[] squareColors = { 255, 255, 0, 255, 0, 255, 255, 255, 0, 0,
0, 0, 255, 0, 255, 255 };
MakeCurrent ();
GL.Viewport (0, 0, Size.Width, Size.Height);
GL.MatrixMode (All.Projection);
GL.LoadIdentity ();
GL.Ortho (-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);
GL.MatrixMode (All.Modelview);
GL.Rotate (3.0f, 0.0f, 0.0f, 1.0f);
GL.ClearColor (0.5f, 0.5f, 0.5f, 1.0f);
GL.Clear ((uint)All.ColorBufferBit);
GL.VertexPointer (2, All.Float, 0, squareVertices);
GL.EnableClientState (All.VertexArray);
GL.ColorPointer (4, All.UnsignedByte, 0, squareColors);
GL.EnableClientState (All.ColorArray);
GL.DrawArrays (All.TriangleStrip, 0, 4);
SwapBuffers ();
}
AsyncHttpPost m_Post;
int m_nPosts = 1;
byte[] LoadPostData()
{
// Just return 3MB of zeros. It doesn't matter whether this is valid POST data or not.
return new byte[1024 * 1024 * 3];
}
void UpdatePost()
{
if ( m_Post == null || m_Post.PostStatus != AsyncHttpPostStatus.InProgress )
{
System.Console.WriteLine( string.Format( "Starting post {0}", m_nPosts++ ) );
byte [] postData = LoadPostData();
m_Post = new AsyncHttpPost(
"https://api-video.facebook.com/restserver.php",
"multipart/form-data; boundary=" + "8cdbcdf18ab6640",
postData );
}
}
Random m_Random = new Random(0);
List< byte [] > m_Allocations;
List< byte[] > m_InitialAllocations;
void SimulateAppAllocations()
{
// First time through, allocate a bunch of data that the app would allocate.
if ( m_InitialAllocations == null )
{
m_InitialAllocations = new List<byte[]>();
int nInitialBytes = 6 * 1024 * 1024;
int nBlockSize = 30000;
for ( int nCurBytes = 0; nCurBytes < nInitialBytes; nCurBytes += nBlockSize )
{
m_InitialAllocations.Add( new byte[nBlockSize] );
}
}
m_Allocations = new List<byte[]>();
for ( int i=0; i < 10; i++ )
{
int nAllocationSize = m_Random.Next( 10000 ) + 10;
m_Allocations.Add( new byte[nAllocationSize] );
}
}
}
public enum AsyncHttpPostStatus
{
InProgress,
Success,
Fail
}
public class AsyncHttpPost
{
public AsyncHttpPost( string sURL, string sContentType, byte [] postData )
{
m_PostData = postData;
m_PostStatus = AsyncHttpPostStatus.InProgress;
m_sContentType = sContentType;
m_sURL = sURL;
//UploadThread();
m_UploadThread = new Thread( new ThreadStart( UploadThread ) );
m_UploadThread.Start();
}
void UploadThread()
{
using ( MonoTouch.Foundation.NSAutoreleasePool pool = new MonoTouch.Foundation.NSAutoreleasePool() )
{
try
{
HttpWebRequest request = WebRequest.Create( m_sURL ) as HttpWebRequest;
request.Method = "POST";
request.ContentType = m_sContentType;
request.ContentLength = m_PostData.Length;
// Write the post data.
using ( Stream stream = request.GetRequestStream() )
{
stream.Write( m_PostData, 0, m_PostData.Length );
stream.Close();
}
System.Console.WriteLine( "Finished!" );
// We're done with the data now. Let it be garbage collected.
m_PostData = null;
// Finished!
m_PostStatus = AsyncHttpPostStatus.Success;
}
catch ( System.Exception e )
{
System.Console.WriteLine( "Error in AsyncHttpPost.UploadThread:
" + e.Message );
m_PostStatus = AsyncHttpPostStatus.Fail;
}
}
}
public AsyncHttpPostStatus PostStatus
{
get
{
return m_PostStatus;
}
}
Thread m_UploadThread;
// Queued to be handled in the main thread.
byte [] m_PostData;
AsyncHttpPostStatus m_PostStatus;
string m_sContentType;
string m_sURL;
}
}
推荐答案
我认为您应该一次读入 1 KB(或任意大小)的文件并将其写入 Web 请求.
I think you should read in your file 1 KB (or some arbitrary size) at a time and write it to the web request.
类似这样的代码:
byte[] buffer = new buffer[1024];
int bytesRead = 0;
using (FileStream fileStream = File.OpenRead("YourFile.txt"))
{
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
{
httpPostStream.Write(buffer, 0, bytesRead);
}
}
这不是我的想法,但我认为这是对的.
This is off the top of my head, but I think it's right.
这样,当您真的不需要时,内存中不会有额外的 3MB 浮动.我认为这样的技巧在 iDevices(或其他设备)上比在桌面上更重要.
This way you don't have an extra 3MB floating around in memory when you don't really need to. I think tricks like this are even more important on iDevices (or other devices) than on the desktop.
也要测试缓冲区大小,更大的缓冲区可以让你获得更好的速度(我记得 8KB 已经很不错了).
Test the buffer size too, a larger buffer will get you better speeds up to a point (I remember 8KB being pretty good).
这篇关于MonoTouch - WebRequest 内存泄漏和崩溃?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!