MonoTouch - WebRequest内存泄漏和崩溃?

时间:2011-03-30 21:11:35

标签: memory-leaks xamarin.ios webrequest

我有一个使用3.5MB文件进行HTTP POST的MonoTouch应用程序,它在我测试的主要平台上非常不稳定(带有OS 3.1.2的iPhone 3G和带有OS 4.2.1的iPhone 4) )。我会描述我在这里做的事情,也许有人可以告诉我,如果我做错了什么。

为了排除我的应用程序的其余部分,我将其缩小为一个小样本应用程序。该应用程序是一个iPhone OpenGL项目,它只做这个:

  1. 启动时,以30k的块分配6MB的内存。这模拟了我的应用程序的内存使用情况。
  2. 将3.5MB文件读入内存。
  3. 创建一个帖子来发布数据。 (创建一个WebRequest对象,使用GetRequestStream(),然后写入3.5MB数据)。
  4. 当主线程检测到发布帖子已完成时,转到步骤2并重复。
  5. 另外,每一帧,我分配0-100k来模拟app做某事。我没有保留对这些数据的任何引用,所以应该收集垃圾。

    iPhone 3G结果:该应用程序上传了6​​到8次,然后操作系统将其杀死。没有崩溃日志,但有一个LowMemory日志显示该应用程序已被丢弃。

    iPhone 4结果:第11次上传时出现Mprotect错误。

    一些数据点:

    • 当应用程序继续上传时,仪器不显示内存增加。
    • 仪器没有显示任何重大泄漏(可能总共1千字节)。
    • 使用一个Stream.Write()调用以64k块或一次性写入发布数据无关紧要。
    • 在开始下一次上传之前,我是否等待响应(HttpWebRequest.HaveResponse)并不重要。
    • POST数据是否有效无关紧要。我尝试过使用有效的POST数据,并尝试发送3MB的零。
    • 如果应用程序没有为每个帧分配任何数据,那么内存耗尽需要更长的时间(但如前所述,我分配每个帧的内存在分配帧之后未被引用,因此它应该被GC舀起来。

    如果没有人有任何想法,我会向Novell提交一个错误,但我想先看看我是否做错了。

    如果有人想要完整的示例应用程序,我可以提供它,但我已经粘贴了下面的EAGLView.cs的内容。

    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:\n" + 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 个答案:

答案 0 :(得分:1)

我认为您应该一次读取1 KB(或任意大小)的文件并将其写入Web请求。

与此类似的代码:

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);
    }
}

这是我的头脑,但我认为这是正确的。

这样,当您不需要时,内存中没有额外的3MB浮动。我觉得这样的技巧在iDevices(或其他设备)上比在桌面上更重要。

测试缓冲区大小,更大的缓冲区可以让你获得更好的速度(我记得8KB非常好)。