Monotouch:不时在单独的线程崩溃中渲染 PDF 页面预览

Posted

技术标签:

【中文标题】Monotouch:不时在单独的线程崩溃中渲染 PDF 页面预览【英文标题】:Monotouch: Render PDF page preview in separate thread crashes from time to time 【发布时间】:2011-10-11 15:47:00 【问题描述】:

我有一个UISlider。它用于快速浏览 PDF。每当达到下一页的阈值时,我都会在滑块的旋钮旁边显示一个UIView,其中包含目标页面的小预览。 滑块代码看起来在此下方(某些部分已剥离)。如果到达下一页,则生成新的预览,否则,沿滑块移动现有的预览。

我得到了各种效果:

如果预览很多页面,应用程序会在

处崩溃

MonoTouch.CoreGraphics.CGContext.Dispose (bool) 10 月 11 日 17:21:13 未知 UIKitApplication:com.brainloop.brainloopbrowser[0x1a2d][2951]:在 MonoTouch.CoreGraphics.CGContext.Finalize ()

或者如果我在最后一个方法中删除了对 Dispose() 的调用:[NSAutoreleasePool release]: This pool has already been released, do not drain it (double release).

通过查看代码,有人知道出了什么问题吗?还是使用线程的整个方法都错了?

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.
;

创建预览的方法正在启动一个新线程。如果用户在线程仍在运行时更改页面,则会中止并预览下一页:

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

为了渲染 PDF 图像,我使用这个:

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;
        

【问题讨论】:

啊啊啊啊!匈牙利符号!我想匈牙利不存在 IDE 工具提示。 说真的,您可以删除对 Thread.Abort() 的调用吗?如果发生在某些事情的中间 Dispose() 上,我会看到一些不好的事情。我认为您可以让线程完成并创建一种使用布尔标志使线程停止(完成工作后)的方法。 但是,如果新线程的创建速度足够快,那么我最终会在渲染图像周围漂浮大量线程,这些线程在任何地方都不会使用。这是一条通用规则:“不要在 MT UI 环境中使用 Thread.Abort()?”我有长时间运行的复杂进度线程,我不想放一个“if(shouldExit) return;”每隔一行。 嗯,我现在已经阅读了很多文档。我同意:Abort() 是邪恶的。我真的必须添加一些智能取消,也许使用 .NET4 的 CancellationToken(如果在 MT 中可用)。如果您的建议解决了我的问题,我会尝试,如果是,则接受答案。 【参考方案1】:

避免Thread.Abort()

是的,这里有一些讨论它的链接:

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

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

如果您可以使用 .Net 4.0 功能,请改用它们。在您的情况下,使用 Task<T> 可能更容易使用。

另外,我认为创建一些限制并仅在用户不活动 25-100 毫秒后启动新线程会很有帮助。

【讨论】:

【参考方案2】:

按照乔纳森的建议摆脱 Thread.Abort()。

不要为每个图像启动一个新线程,而是让一个带有工作队列的后台线程。然后只需将最重要的页面放在队列的前面,以便尽快呈现。您还可以选择限制队列的大小或从中删除不需要的项目。

【讨论】:

以上是关于Monotouch:不时在单独的线程崩溃中渲染 PDF 页面预览的主要内容,如果未能解决你的问题,请参考以下文章

Monotouch内存限制崩溃

在 MonoTouch 应用程序中使用“气泡消息”聊天时,本机堆栈跟踪崩溃

多线程渲染仅在 iOS 13 上崩溃

为什么MonoTouch在此简单代码上崩溃?

使用 MFMailComposeViewController 时 Monotouch iOS 6 崩溃

使用 TestFlight 时更好的 MonoTouch 崩溃