Thread.Abort在catch {}语句中导致死锁

时间:2012-01-24 00:25:33

标签: c# multithreading try-catch deadlock thread-abort

我有一组打印不同类型文档的线程类。这些类使用继承来共享公共代码。类构造函数需要文件名和打印机名称参数。 Print()方法创建一个新的工作线程,等待工作线程使用Thread.Join(timeout)完成,如果Thread.Abort()超时,则在工作线程上调用Join。工作线程启动一个可以打开指定文件的应用程序,使文件同步发送到打印机(通常使用应用程序的Print方法)并退出。工作线程的代码包含在try{} ... catch{}块中,以处理外部应用程序的任何无法预料的崩溃。 catch块包含最少的清理和日志记录。

    internal static FilePackage TryPrintDocumentToPdf(string Filename)
    {
                .....

                Logging.Log("Printing this file using PowerPoint.", Logging.LogLevel.Debug);
                printableFormat = true;

                fc = new FileCollector(Email2Pdf.Settings.Printer.PdfAttachmentCollectDirectoryObj, FileCollector.CollectMethods.FileCount | FileCollector.CollectMethods.FilesNotInUse | FileCollector.CollectMethods.ProcessExit);
                fc.FileCount = 1;
                fc.ProcessNames = new string[] { OfficePowerPointExe, Email2Pdf.Settings.Printer.PrinterExe };
                fc.Prepare();

                using (PowerPointPrinter printer = new PowerPointPrinter(Filename, Email2Pdf.Settings.Printer.PdfAttachmentPrinter))
                {
                    printer.KillApplicationOnClose = true;
                    printer.Print();
                    printOk = printer.PrintOk;
                }

                .....
    }

    internal abstract class ApplicationPrinter : IDisposable
    {
        protected abstract string applicationName { get; }

        protected string filename;
        protected string printer;

        protected bool workerPrintOk;
        protected bool printOk;
        public bool PrintOk { get { return printOk; } }
        public bool KillApplicationOnClose { get; set; }

        public void Print()
        {
            System.Threading.Thread worker = new System.Threading.Thread(printWorker);
            DateTime time = DateTime.Now;
            worker.Start();

            if (worker.Join(new TimeSpan(0, Email2Pdf.Settings.Printer.FileGenerateTimeOutMins, 0)))
            {
                printOk = workerPrintOk;
            }
            else
            {
                worker.Abort();
                printOk = false;
                Logging.Log("Timed out waiting for " + applicationName + " file " + filename + " to print.", Logging.LogLevel.Error);
            }
        }

        protected abstract void Close();
        protected abstract void printWorker();

        public virtual void Dispose() { Close(); }
    }
    internal class PowerPointPrinter : ApplicationPrinter
    {
        private const string appName = "PowerPoint";
        protected override string applicationName { get { return appName; } }
        private Microsoft.Office.Interop.PowerPoint.Application officePowerPoint = null;

        public PowerPointPrinter(string Filename, string Printer)
        {
            filename = Filename;
            printer = Printer;
            this.Dispose();
        }

        protected override void printWorker()
        {
            try
            {
                officePowerPoint = new Microsoft.Office.Interop.PowerPoint.Application();
                officePowerPoint.DisplayAlerts = Microsoft.Office.Interop.PowerPoint.PpAlertLevel.ppAlertsNone;

                Microsoft.Office.Interop.PowerPoint.Presentation doc = null;

                doc = officePowerPoint.Presentations.Open(
                    filename,
                    Microsoft.Office.Core.MsoTriState.msoTrue,
                    Microsoft.Office.Core.MsoTriState.msoFalse,
                    Microsoft.Office.Core.MsoTriState.msoFalse);
                doc.PrintOptions.ActivePrinter = printer;
                doc.PrintOptions.PrintInBackground = Microsoft.Office.Core.MsoTriState.msoFalse;
                doc.PrintOptions.OutputType = Microsoft.Office.Interop.PowerPoint.PpPrintOutputType.ppPrintOutputSlides;
                doc.PrintOut();

                System.Threading.Thread.Sleep(500);

                doc.Close();
                //Marshal.FinalReleaseComObject(doc);
                doc = null;

                workerPrintOk = true;
            }
            catch (System.Exception ex)
            {
                Logging.Log("Unable to print PowerPoint file " + filename + ". Exception: " + ex.Message, Logging.LogLevel.Error);
                Close();
                workerPrintOk = false;
            }
        }

        protected override void Close()
        {
            try
            {
                if (officePowerPoint != null)
                    officePowerPoint.Quit();
                Marshal.FinalReleaseComObject(officePowerPoint);
                officePowerPoint = null;
                if (KillApplicationOnClose)
                    Utility.KillProcessesByName(OfficePowerPointExe);
            }
            catch { }
        }
    }

我发现我的应用程序没有响应,主线程在Thread.Abort()行的Sleep / Wait / Join中。我不记得工作线程的状态,但是应该在catch{}块中执行的日志记录没有发生。 (我发现它没有响应后,我附加到VS2010我的进程。)

我参考Thread.Abort Method中的以下注意

  

调用Abort的线程可能会阻塞正在进行的线程   中止是在受保护的代码区域中,例如catch块,   最后阻塞或约束执行区域。如果线程那个   调用Abort持有一个被中止线程需要的锁,一个死锁   可能会发生。

我认为我有一个死锁问题,因为(1)它并不总是发生,(2)因为MSDN上的注意(上图)。

  1. 注意似乎表明,如果线程可以try{} ... catch{}编辑,则在线程内使用Abort() NEVER 是安全的。这是真的吗?
  2. 我看不出如何避免在我的场景中使用Abort()。使用Thread.Interrupt()代替会有什么不同吗?
  3. 如何解决我遇到的死锁问题?
  4. BackgroundWorker对我不起作用,因为我不需要进度报告,更重要的是,我的工作线程可能会在执行第三方应用程序时无限期地阻塞。出于同样的原因,我不能要求我的线程终止,但只有一个选项 - 无情地Abort()工作线程。

3 个答案:

答案 0 :(得分:3)

使用Thread.Abort()的机制不是很好。实际上,应该避免调用Thread.Abort()

  

调用Abort的线程可能会阻塞正在进行的线程   中止是在受保护的代码区域中,例如catch块,   最后阻塞或约束执行区域。如果线程那个   调用Abort持有一个被中止线程需要的锁,一个死锁   可以发生。 Ref

相反,使用BackgroundWorker支持取消,进度报告(以及在已完成事件中自动编组到UI线程)。

答案 1 :(得分:0)

在我看来,您基本上是远程控制PowerPoint应用程序以打印PowerPoint文档。因此,您可能会受到应用程序在屏幕上显示(或试图提出)的任何对话框的影响。如果整个事情在后台运行(例如在服务器上),则可能没有用户解雇任何此类对话框,因此可以解释部分问题。我的建议是调查第三方库,这些库允许您加载PPT文件并将其打印(或将其转换为PDF并打印),而不必依赖PowerPoint应用程序。然后你不必等待你控制之外的外部应用程序,你就不必采取强制中止线程。

答案 2 :(得分:0)

我认为通过进行以下更改找到了解决方案:

  1. 如果我们知道工作线程正在执行Thread.Abort()阻止(请参阅下面的catch{}),请不要致电protected volatile bool isPrinting
  2. 使用单独的帖子来呼叫Thread.Abort(),并鼓励使用Sleep(0)进行上下文切换(请参阅下面的private void AbortPrintWorker())。

    internal abstract class ApplicationPrinter : IDisposable
    {
        protected abstract string applicationName { get; }
    
        protected string filename;
        protected string printer;
    
        protected bool workerPrintOk;
        protected bool printOk;
        public bool PrintOk { get { return printOk; } }
        public bool KillApplicationOnClose { get; set; }
    
        protected System.Threading.Thread worker;
        protected volatile bool isPrinting;
    
        public void Print()
        {
            worker = new System.Threading.Thread(printWorker);
            DateTime time = DateTime.Now;
            worker.Start();
    
            if (worker.Join(new TimeSpan(0, Email2Pdf.Settings.Printer.FileGenerateTimeOutMins, 0)))
            {
                printOk = workerPrintOk;
            }
            else
            {
                AbortPrintWorker();
                printOk = false;
                Logging.Log("Timed out waiting for " + applicationName + " file " + filename + " to print.", Logging.LogLevel.Error);
            }
        }
        protected abstract void printWorker();
    
        public abstract void Dispose();
    
        private void AbortPrintWorker()
        {
            System.Threading.Thread abortThread = new System.Threading.Thread(abortWorker);
            if (isPrinting)
            {
                abortThread.Start();
                System.Threading.Thread.Sleep(0);
                abortThread.Join();
            }
            else
            {
                worker.Join();
            }
        }
    
        private void abortWorker()
        {
            worker.Abort();
            worker.Join();
        }
    }
    
    internal class PowerPointPrinter : ApplicationPrinter
    {
        private const string appName = "PowerPoint";
        protected override string applicationName { get { return appName; } }
        private Microsoft.Office.Interop.PowerPoint.Application officePowerPoint = null;
    
        public PowerPointPrinter(string Filename, string Printer)
        {
            filename = Filename;
            printer = Printer;
            this.Dispose();
        }
    
        protected override void printWorker()
        {
            try
            {
                isPrinting = true;
    
                officePowerPoint = new Microsoft.Office.Interop.PowerPoint.Application();
                officePowerPoint.DisplayAlerts = Microsoft.Office.Interop.PowerPoint.PpAlertLevel.ppAlertsNone;
    
                Microsoft.Office.Interop.PowerPoint.Presentation doc = null;
    
                doc = officePowerPoint.Presentations.Open(
                    filename,
                    Microsoft.Office.Core.MsoTriState.msoTrue,
                    Microsoft.Office.Core.MsoTriState.msoFalse,
                    Microsoft.Office.Core.MsoTriState.msoFalse);
                doc.PrintOptions.ActivePrinter = printer;
                doc.PrintOptions.PrintInBackground = Microsoft.Office.Core.MsoTriState.msoFalse;
                doc.PrintOptions.OutputType = Microsoft.Office.Interop.PowerPoint.PpPrintOutputType.ppPrintOutputSlides;
                doc.PrintOut();
    
                System.Threading.Thread.Sleep(500);
    
                doc.Close();
                Marshal.FinalReleaseComObject(doc);
                doc = null;
    
                workerPrintOk = true;
    
                isPrinting = true;
            }
            catch (System.Exception ex)
            {
                isPrinting = false;
    
                Logging.Log("Unable to print PowerPoint file " + filename + ". Exception: " + ex.Message, Logging.LogLevel.Error);
                workerPrintOk = false;
            }
        }
    
        public override void Dispose()
        {
            try
            {
                if (officePowerPoint != null)
                    officePowerPoint.Quit();
                Marshal.FinalReleaseComObject(officePowerPoint);
                officePowerPoint = null;
                if (KillApplicationOnClose)
                    Utility.KillProcessesByName(OfficePowerPointExe);
            }
            catch { }
        }
    }
    
  3. AbortPrintWorker()创建一个单独的线程来调用工作线程上的Abort()。我相信这会处理Abort()注意中突出显示的问题:

      

    调用Abort的线程可能会阻塞正在进行的线程   中止是在受保护的代码区域中,例如catch块,   最后阻塞或约束执行区域。如果线程那个   调用Abort持有一个被中止线程需要的锁,一个死锁   可能会发生。

    这是对的吗?