Question

J'ai un ensemble de classes threadées qui impriment différents types de documents.Les classes utilisent l'héritage pour partager du code commun.Le constructeur de classe nécessite des arguments de nom de fichier et de nom d'imprimante.UN Print() La méthode crée un nouveau thread de travail, attend que le thread de travail se termine en utilisant Thread.Join(timeout) et appelle Thread.Abort() sur le thread de travail si le Join Le temps est écoulé.Le thread de travail démarre une application qui peut ouvrir le fichier spécifié, envoie le fichier à l'imprimante de manière synchrone (généralement à l'aide de la méthode Print de l'application) et se ferme.Le code du thread de travail est enveloppé dans un try{} ... catch{} bloquer pour faire face à tout plantage imprévu de l’application externe.Le bloc catch contient un nettoyage et une journalisation minimes.

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

J'ai trouvé mon application non réactive, avec le thread principal dans un mode Sleep/Wait/Join sur la ligne Thread.Abort().Je ne me souviens pas de l'état du thread de travail, mais de la journalisation qui était censée être effectuée dans le catch{} le blocage n’a pas eu lieu.(JE Ci-joint à mon processus avec VS2010 après l'avoir trouvé non réactif).

Je fais référence à ce qui suit Note du Méthode Thread.Abort:

Le thread qui appelle abandonner peut bloquer si le thread qui est abandonné se trouve dans une région de code protégée, comme un bloc de capture, un blocage enfin ou une région d'exécution contrainte.Si le thread qui appelle Abortive contient une serrure dont le thread abandonné nécessite, une impasse peut se produire.

Je crois que j'ai un problème d'impasse parce que (1) cela n'arrive pas toujours, et (2) à cause du Note sur MSDN (ci-dessus).

  1. La note semble suggérer que l'utilisation try{} ... catch{} est JAMAIS en sécurité à l'intérieur d'un fil si le fil peut être Abort()'éd.Est-ce vrai?
  2. Je ne vois pas comment éviter d'utiliser Abort() dans mon scénario.Utilisera-t-il Thread.Interrupt() au lieu de cela, cela fait-il une différence ?
  3. Comment puis-je résoudre mon problème de blocage ?

BackgroundWorker ne fonctionne pas pour moi car je n'ai pas besoin de rapport de progression et, plus important encore, il est possible que mon thread de travail se bloque indéfiniment lorsqu'il exécute des applications tierces.Pour la même raison, je ne peux pas demander mon fil de discussion doit se terminer, mais je n'ai qu'une seule option - impitoyablement Abort() le thread de travail.

Était-ce utile?

La solution 3

Je pense avoir trouvé une solution en apportant les modifications suivantes :

  1. Ne pas appeler Thread.Abort() si nous savoir que le thread de travail exécute un catch{} bloc (voir protected volatile bool isPrinting ci-dessous).
  2. Utilisez un fil de discussion séparé pour appeler Thread.Abort() et encourager un changement de contexte avec Sleep(0) (voir private void AbortPrintWorker() ci-dessous).

    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() crée un fil de discussion séparé à appeler Abort() sur le thread de travail.Je pense que cela répond au problème souligné dans le Note sur Abort():

Le thread qui appelle Abort peut bloquer si le thread en cours d’exécution aborted se trouve dans une zone protégée du code, telle qu’un bloc catch, finally block, ou une région d’exécution contrainte.Si le thread qui appelle Abort détient un verrou requis par le thread abandonné, un interverrou peut se produire.

Est-ce correct?

Autres conseils

Votre mécanisme utilisant Thread.Abort() n'est pas une bonne chose.En fait, appeler Thread.Abort() devrait être évité.

Le thread qui appelle abandonner peut bloquer si le thread qui est abandonné se trouve dans une région de code protégée, comme un bloc de capture, un blocage enfin ou une région d'exécution contrainte.Si le thread qui appelle Abortive contient une serrure dont le thread abandonné nécessite, une impasse peut se produire. Réf.

Utilisez plutôt un Travailleur d'arrière-plan qui prend en charge l'annulation, les rapports de progression (et le regroupement automatique sur le fil de l'interface utilisateur en cas d'événement terminé).

Il me semble que vous contrôlez à distance l'application PowerPoint afin d'imprimer un document PowerPoint.Ainsi, vous pourriez être soumis à toutes les boîtes de dialogue que l'application affiche (ou tente d'afficher) à l'écran.Si tout cela est exécuté en arrière-plan (par ex.sur un serveur), il se peut qu'aucun utilisateur ne rejette de telles boîtes de dialogue, ce qui pourrait expliquer une partie du problème.Ma recommandation serait de rechercher des bibliothèques tierces qui vous permettraient de charger un fichier PPT et de l'imprimer (ou de le convertir en PDF et de l'imprimer) sans avoir à recourir à l'application PowerPoint.Vous n’aurez alors pas à attendre une application externe hors de votre contrôle et vous n’aurez pas à recourir à l’abandon forcé des threads.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top