MonoTouch - WebRequest утечка памяти и сбой? - PullRequest
5 голосов
/ 31 марта 2011

У меня есть приложение MonoTouch, которое выполняет HTTP POST с файлом 3,5 МБ, и оно очень нестабильно на основных платформах, на которых я тестирую (iPhone 3G с OS 3.1.2 и iPhone 4 с OS 4.2.1).Я опишу, что я делаю здесь, и, может быть, кто-то может сказать мне, если я делаю что-то не так.

Чтобы исключить остальную часть моего приложения, я сократил это до крошечногопример приложения.Приложение представляет собой iPhone OpenGL Project, и оно делает только это:

  1. При запуске выделите 6 МБ памяти в 30 000 блоков.Это моделирует использование памяти моего приложения.
  2. Считать файл 3,5 МБ в память.
  3. Создать тему для публикации данных.(Создайте объект WebRequest, используйте GetRequestStream () и запишите данные размером 3,5 МБ).
  4. Когда основной поток обнаружит, что поток публикации завершен, перейдите к шагу 2. и повторите.

Кроме того, для каждого кадра я выделяю 0-100k, чтобы смоделировать приложение, выполняющее что-либо.Я не храню никаких ссылок на эти данные, поэтому они должны собирать мусор.

Результат iPhone 3G: Приложение получает от 6 до 8 загрузок, а затем ОС убивает его.Журнала сбоев нет, но есть журнал LowMemory, показывающий, что приложение было отброшено.

iPhone 4 Результат: При 11-й загрузке возникает ошибка Mprotect.

Несколько точек данных:

  • Инструменты НЕ отображаютсяпамять продолжает увеличиваться по мере того, как приложение продолжает загружаться.
  • Приборы не показывают значительных утечек (возможно, всего 1 килобайт).
  • Не имеет значения, пишу ли я данные поста в 64kкуски или все сразу с одним вызовом Stream.Write ().
  • Не имеет значения, жду ли я ответа (HttpWebRequest.HaveResponse) или нет перед началом следующей загрузки.
  • Не имеет значения, являются ли данные POST действительными.Я попытался использовать действительные данные POST и попытался отправить 3 МБ нулей.
  • Если приложение не распределяет какие-либо данные в каждом кадре, потребуется больше времени для исчерпания памяти (но, как упоминалось ранее,память, на которую я выделяю каждый кадр, не указывается после кадра, на который он был выделен, поэтому он должен быть удален 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 Ответ

1 голос
/ 31 марта 2011

Я думаю, что вы должны прочитать в своем файле 1 КБ (или некоторый произвольный размер) за раз и записать его в веб-запрос.

Код похож на этот:

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

Это не в моей голове, но я думаю, что это правильно.

Таким образом, у вас нет лишних 3 МБ памяти, которые вам не нужны. Я думаю, что подобные хитрости еще более важны для iDevices (или других устройств), чем для настольных компьютеров.

Также проверьте размер буфера, больший буфер даст вам лучшую скорость до определенного момента (я помню, 8KB довольно неплохо).

...