У меня есть набор многопоточных классов, которые печатают различные типы документов. Классы используют наследование для совместного использования общего кода. Конструктор класса требует аргументов имени файла и имени принтера. Метод 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 (выше).
- В примечании указывается, что использование
try{} ... catch{}
является НИКОГДА безопасным внутри потока, если поток может быть Abort()
'ed. Это правда?
- Я не вижу, как можно избежать использования
Abort()
в моем сценарии. Будет ли использование Thread.Interrupt()
иметь какое-либо значение?
- Как мне исправить проблему с блокировкой?
BackgroundWorker не работает для меня, потому что мне не нужны отчеты о прогрессе, и, что более важно, возможно, что мой рабочий поток будет блокироваться бесконечно, когда он выполняет сторонние приложения. По той же причине я не могу попросить завершить мой поток, но у меня есть только один вариант - безжалостно Abort()
рабочий поток.