Thread.Abort вызывает тупик в операторе catch {} - PullRequest
0 голосов
/ 24 января 2012

У меня есть набор многопоточных классов, которые печатают различные типы документов. Классы используют наследование для совместного использования общего кода. Конструктор класса требует аргументов имени файла и имени принтера. Метод Print() создает новый рабочий поток, ожидает завершения рабочего потока с использованием Thread.Join(timeout) и вызывает Thread.Abort() в рабочем потоке, если время Join истекло. Рабочий поток запускает приложение, которое может открыть указанный файл, вызывает отправку файла на принтер синхронно (обычно с использованием метода печати приложения) и завершается. Код рабочего потока заключен в блок 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 { }
        }
    }

Я обнаружил, что мое приложение не отвечает, с основным потоком в строке Sleep / Wait / Join в строке Thread.Abort (). Я не помню состояние рабочего потока, но запись в журнал, которая должна была выполняться в блоке catch{}, не состоялась. (Я прикрепил к моему процессу с VS2010 после того, как обнаружил, что он не отвечает).

Я имею в виду следующее Примечание из Thread.Abort Method :

Поток, который вызывает Abort, может блокироваться, если поток, который находится в процессе Прерванный находится в защищенной области кода, такой как блок catch, наконец, блок или ограниченная область выполнения. Если нить то вызывает Abort содержит блокировку, которая требуется для прерванного потока, тупик может произойти.

Я полагаю, что у меня проблема с тупиковой блокировкой, потому что (1) это не всегда происходит и (2) из-за Note в MSDN (выше).

  1. В примечании указывается, что использование try{} ... catch{} является НИКОГДА безопасным внутри потока, если поток может быть Abort() 'ed. Это правда?
  2. Я не вижу, как можно избежать использования Abort() в моем сценарии. Будет ли использование Thread.Interrupt() иметь какое-либо значение?
  3. Как мне исправить проблему с блокировкой?

BackgroundWorker не работает для меня, потому что мне не нужны отчеты о прогрессе, и, что более важно, возможно, что мой рабочий поток будет блокироваться бесконечно, когда он выполняет сторонние приложения. По той же причине я не могу попросить завершить мой поток, но у меня есть только один вариант - безжалостно Abort() рабочий поток.

Ответы [ 3 ]

3 голосов
/ 24 января 2012

Ваш механизм, использующий Thread.Abort(), не очень хорош. На самом деле, следует избегать вызова Thread.Abort().

Поток, который вызывает Abort, может заблокироваться, если поток Прерванный находится в защищенной области кода, такой как блок catch, наконец, блок или ограниченная область выполнения. Если нить то вызывает Abort содержит блокировку, которая требуется для прерванного потока, тупик может возникнуть. Ref .

Вместо этого используйте BackgroundWorker , который поддерживает отмену, отчеты о ходе выполнения (и автоматическое маршалинг в поток пользовательского интерфейса в завершенном событии).

0 голосов
/ 24 января 2012

Я думаю, что нашел решение, внеся следующие изменения:

  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 { }
        }
    }
    

AbortPrintWorker() создает отдельный поток для вызова Abort() в рабочем потоке. Я считаю, что это связано с проблемой, отмеченной в Note на Abort():

Поток, который вызывает Abort, может блокироваться, если поток, который Прерванный находится в защищенной области кода, такой как блок catch, наконец, блок или ограниченная область выполнения. Если нить то вызывает Abort содержит блокировку, которая требуется для прерванного потока, тупик может произойти.

Это правильно?

0 голосов
/ 24 января 2012

Мне кажется, что вы в основном управляете приложением PowerPoint, чтобы распечатать документ PowerPoint.Таким образом, вы можете столкнуться с любыми диалоговыми окнами, которые приложение выводит (или пытается установить) на экране.Если все это выполняется в фоновом режиме (например, на сервере), пользователь может не закрывать какие-либо такие диалоги, что могло бы объяснить часть проблемы.Я рекомендую изучить сторонние библиотеки, которые позволят вам загрузить PPT-файл и распечатать его (или преобразовать в PDF-файл и распечатать его), не полагаясь на приложение PowerPoint.Тогда вам не придется ждать на внешнем приложении вне вашего контроля, и вам не придется прибегать к принудительному прерыванию потоков.

...