开发者

Monotouch: Render PDF page preview in separate thread crashes from time to time

开发者 https://www.devze.com 2023-04-12 19:28 出处:网络
I have a UISlider. It is used to navigate quickly through a PDF. Whenever the threshold for the next page is reached, I display a UIView next to the slider\'s knob that contains a small preview of the

I have a UISlider. It is used to navigate quickly through a PDF. Whenever the threshold for the next page is reached, I display a UIView next to the slider's knob that contains a small preview of the target page. The slider code looks is below this (some parts stripped). If the next page is reached, a new preview is generated, Otherwise, the existing one is moved along the slider.

I get various effects:

  • If previewing many many pages, the app crashes at

    MonoTouch.CoreGraphics.CGContext.Dispose (bool) <0x00047> Oct 11 17:21:13 unknown UIKitApplication:com.brainloop.brainloopbrowser[0x1a2d][2951] : at MonoTouch.CoreGraphics.CGContext.Finalize () <0x0002f>

  • or if I remove the calls to Dispose() in the last method: [NSAutoreleasePool release]: This pool has already been released, do not drain it (double release).开发者_JS百科

From looking at the code, does somebody have an idea what's wrong? Or is the whole approach of using a thread wrong?

this.oScrollSlider = new UISlider ();
this.oScrollSlider.TouchDragInside += delegate( object oSender, EventArgs oArgs )
{
this.iCurrentPage = (int)Math.Round (oScrollSlider.Value);
    if (this.iCurrentPage != this.iLastScrollSliderPage)
    {
        this.iLastScrollSliderPage = this.iCurrentPage;
        this.RenderScrollPreviewImage(this.iCurrentPage);
    }
};
this.oScrollSlider.ValueChanged += delegate
{
    if (this.oScrollSliderPreview != null)
    {
        this.oScrollSliderPreview.RemoveFromSuperview ();
        this.oScrollSliderPreview.Dispose();
        this.oScrollSliderPreview = null;
    }
    // Go to the selected page.
};

The method creating the preview is spinning off a new thread. If the user changes pages whil the thread is still going, it is aborted and the next page is previewed:

private void RenderScrollPreviewImage (int iPage)
{
// Create a new preview view if not there.  
if(this.oScrollSliderPreview == null)
    {
        SizeF oSize = new SizeF(150, 200);
        RectangleF oFrame = new RectangleF(new PointF (this.View.Bounds.Width - oSize.Width - 50, this.GetScrollSliderOffset (oSize)), oSize);
        this.oScrollSliderPreview = new UIView(oFrame);
        this.oScrollSliderPreview.BackgroundColor = UIColor.White;
        this.View.AddSubview(this.oScrollSliderPreview);    

        UIActivityIndicatorView oIndicator = new UIActivityIndicatorView(UIActivityIndicatorViewStyle.Gray);
        oIndicator.Center = new PointF(this.oScrollSliderPreview.Bounds.Width/2, this.oScrollSliderPreview.Bounds.Height/2);
        this.oScrollSliderPreview.AddSubview(oIndicator);
        oIndicator.StartAnimating();
}
            // Remove all subviews, except the activity indicator.
            if(this.oScrollSliderPreview.Subviews.Length > 0)
            {
                foreach(UIView oSubview in this.oScrollSliderPreview.Subviews)
                {
                    if(!(oSubview is UIActivityIndicatorView))
                    {
                        oSubview.RemoveFromSuperview();
                    }
                }
            }

// Kill the currently running thread that renders a preview.
            if(this.oRenderScrollPreviewImagesThread != null)
            {
                try
                {
                    this.oRenderScrollPreviewImagesThread.Abort();
                }
                catch(ThreadAbortException)
                {
                    // Expected.
                }
            }

// Start a new rendering thread.
            this.oRenderScrollPreviewImagesThread = new Thread (delegate()
            {
                using (var oPool = new NSAutoreleasePool())
                {
                    try
                    {
// Create a quick preview.
                        UIImageView oImgView = PdfViewerHelpers.GetLowResPagePreview (this.oPdfDoc.GetPage (iPage), new RectangleF (0, 0, 150, 200));
                        this.InvokeOnMainThread(delegate
                        {
                            if(this.oScrollSliderPreview != null)
                            {
                                oImgView.Center = new PointF(this.oScrollSliderPreview.Bounds.Width/2, this.oScrollSliderPreview.Bounds.Height/2);
// Add the PDF image to the preview view.                               
this.oScrollSliderPreview.AddSubview(oImgView);
                            }
                        });
                    }
                    catch (Exception)
                    {
                    }
                }
            });
// Start the thread.
            this.oRenderScrollPreviewImagesThread.Start ();
        }

To render the PDF image, I use this:

internal static UIImageView GetLowResPagePreview (CGPDFPage oPdfPage, RectangleF oTargetRect)
        {
            RectangleF oPdfPageRect = oPdfPage.GetBoxRect (CGPDFBox.Media);

            // If preview is requested for the PDF index view, render a smaller version.
            float fAspectScale = 1.0f;
            if (!oTargetRect.IsEmpty)
            {
                fAspectScale = GetAspectZoomFactor (oTargetRect.Size, oPdfPageRect.Size, false);
                // Resize the PDF page so that it fits the target rectangle.
                oPdfPageRect = new RectangleF (new PointF (0, 0), GetFittingBox (oTargetRect.Size, oPdfPageRect.Size));
            }

            // Create a low res image representation of the PDF page to display before the TiledPDFView
            // renders its content.
            int iWidth = Convert.ToInt32 ( oPdfPageRect.Size.Width );
            int iHeight = Convert.ToInt32 ( oPdfPageRect.Size.Height );
            CGColorSpace oColorSpace = CGColorSpace.CreateDeviceRGB();
            CGBitmapContext oContext = new CGBitmapContext(null, iWidth, iHeight, 8, iWidth * 4, oColorSpace, CGImageAlphaInfo.PremultipliedLast);

            // First fill the background with white.
            oContext.SetFillColor (1.0f, 1.0f, 1.0f, 1.0f);
            oContext.FillRect (oPdfPageRect);

            // Scale the context so that the PDF page is rendered 
            // at the correct size for the zoom level.
            oContext.ScaleCTM (fAspectScale, fAspectScale);
            oContext.DrawPDFPage (oPdfPage);

            CGImage oImage = oContext.ToImage();
            UIImage oBackgroundImage = UIImage.FromImage( oImage);
            oContext.Dispose();
            oImage.Dispose ();
            oColorSpace.Dispose ();

            UIImageView oBackgroundImageView = new UIImageView (oBackgroundImage);
            oBackgroundImageView.Frame = new RectangleF (new PointF (0, 0), oPdfPageRect.Size);
            oBackgroundImageView.ContentMode = UIViewContentMode.ScaleToFill;
            oBackgroundImageView.UserInteractionEnabled = false;
            oBackgroundImageView.AutoresizingMask = UIViewAutoresizing.None;

            return oBackgroundImageView;
        }


Avoid Thread.Abort().

Yeah, here are some links talking about it:

http://www.interact-sw.co.uk/iangblog/2004/11/12/cancellation

http://haacked.com/archive/2004/11/12/how-to-stop-a-thread.aspx

If you can use the .Net 4.0 features, go with them instead. Using a Task<T> is probably easier to work with in your case.

Also, I think it would be helpful to create some throttling and only start your new thread after 25-100 milliseconds of inactivity from the user.


Get rid of Thread.Abort() as Jonathan suggests.

Don't start a new thread for each image and instead have one background thread with a work queue. Then simply put the most important page to the front of the queue so it renders as soon as possible. You can also optionally limit the size of the queue or remove unneeded items from it.

0

精彩评论

暂无评论...
验证码 换一张
取 消