The problem is that dispatcher blocks the MainWindow thread until it is executed all of the queued AddOutputLine()s. Can these calls be non-blocking for a wpf control?
//TaskPage.xaml.cs
[MTAThread]
public void AddOutputLine(string line = "")
{
try {
Session.TaskPage.Dispatcher.BeginInvoke(new Action(delegate
{
txtOutput.Text += "[" + DateTime.Now + "] " + line + Environment.NewLine;
txtOutput.ScrollToEnd();
}));
} catch (Exception ex) {
Program.SetCurrentStatus("Error occured : "+ex.Message);
}
}
In this class that invokes the Action parameter:
//BackgroundUploader.cs
private Action<string> _log;
public BackgroundUploader(Client client, Action<string> log = null)
{
_log = log;
_client = client;
}
while doing this:
//BackgroundUploader.cs
public void RunThreadProc()
{
try
{
_log?.Invoke("Timeout interval set for BackGround Task.");
while (!Stop)
{
//the problematic blocking of WPF Control starts here
var inOutFiles = CreateXML(_sqlStrings, _outputNames, _outputDirectory);
Send(inOutFiles, _outputDirectory);
LastRunTime = DateTime.Now;
_log?.Invoke($"Waiting for {_waitMinutes} minutes...");
//the Control returns to processing messages
System.Threading.Thread.Sleep((int)TimeSpan.FromMinutes(_waitMinutes).TotalMilliseconds);
}
_log?.Invoke("BackGroundTask canceled.");
}
catch (Exception ex)
{
_log?.Invoke($"Error while executing background task: {ex.Message}");
}
}
The RunThreadProc() is called from Control like this:
//TaskPage.xaml.cs
_backgroundThread = new System.Threading.Thread(bU.RunThreadProc);
_backGroundThread.Start();
I also cannot use Bcl.Async because it's failing on Task result with method that uses async modifier on Windows Server 2003 target OS.
edit: I encountered the problem inside of the background class, its methods run synchronously, even though they are in a different thread.
Related
I have an async function which still freezes / lags the UI thread for me when I execute it. This is my function calling it.
private void TcpListenerLogic(object sender, string e)
{
Application.Current.Dispatcher.BeginInvoke((Action)async delegate {
try
{
dynamic results = JsonConvert.DeserializeObject<dynamic>(e);
if (results.test_id != null)
{
// Get properties for new anchor
string testInformation = await CommunicationCommands.getJsonFromURL(
"http://" + ServerIP + ":" + ServerPort + "/api/" + results.test_id);
}
}
catch (Exception exception)
{
// Writing some Trace.WriteLine()'s
}
});
}
And this is the async function that freezes my UI Thread
public static async Task<string> getJsonFromURL(string url)
{
try
{
string returnString = null;
using (System.Net.WebClient client = new System.Net.WebClient())
{
returnString = await client.DownloadStringTaskAsync(url);
}
return returnString;
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
return null;
}
}
I already tried to make everything in TcpListenerLogic run in a new Thread:
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
}).Start();
Which resulted in the whole UI completely freezing. And I tried to make TcpListenerLogic async and await the dispatcher, which also made everything freeze permanently. I also tried to make TcpListenerLogic async and leave the dispatcher. The dispatcher is only there because I normally have some UI code in there, which I left out for my tests.
I have ventured far through the internet, but no BackgroundWorker, ThreadPool or other methods helped me in my endeavour.
If anyone has help for this particular problem, or a resource that would improve my understanding of async functions in C#, I would much appreciate it.
Edit
As requested a deeper insight in how this event handler is called.
I have System.Net.Websocket, which is connected to the Backend API I am working with and triggers an event, everytime he receives new Data. To guarantee the socket listens as longs as it is open, there is a while loop which checks for the client state:
public event EventHandler<string> TcpReceived;
public async void StartListener(string ip, int port, string path)
{
try
{
using (client = new ClientWebSocket())
{
try
{ // Connect to backend
Uri serverUri = new Uri("ws://" + ip + ":" + port.ToString() + path );
await client.ConnectAsync(serverUri, CancellationToken.None);
}
catch (Exception ex)
{
BackendSettings.IsConnected = false;
Debug.WriteLine("Error connecting TCP Socket: " + ex.ToString());
}
state = client.State;
// Grab packages send in backend
while (client.State == WebSocketState.Open || client.State == WebSocketState.CloseSent)
{
try
{
// **Just formatting the received data until here and writing it into the "message" variable**//
TcpReceived(this, message);
// Close connection on command
if (result.MessageType == WebSocketMessageType.Close)
{
Debug.WriteLine("Closing TCP Socket.");
shouldstayclosed = true;
await client.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
break;
}
state = client.State;
}
catch
{
BackendSettings.IsConnected = false;
state = client.State;
}
}
state = client.State;
}
}
catch (Exception ex)
{
// Some error messages and settings handling
}
}
The Event has a handler attached:
TcpReceived += TcpListener_TcpReceived;
And this is the Handler, which calls the previously seen "TcpListenereLogic".
private void TcpListener_TcpReceived(object sender, string e)
{
TcpListenerLogic(sender, e);
//App.Current.Dispatcher.BeginInvoke(new Action(() => {
// TcpListenerLogic(sender, e);
//}));
//new Thread(() =>
//{
// Thread.CurrentThread.IsBackground = true;
// TcpListenerLogic(sender, e);
//}).Start();
}
I previously had the "TcpListenereLogic" as the handler, but I wanted to try different methods to call it. I also left in the commented out part, to show how the call of "TcpListenereLogic" looked already. All my attempts were with all mentioned setups and sadly lead to nothing.
Thank you very much #TheodorZoulias for helping me to find the solution to my problem.
It turns out it wasn't the async function itself, but rather how often it gets called. It got called roughly ~120 times every second.
My solution starts by calling the Listener method over a new Thread:
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
MainWindow.tcpListener.StartListener(ip, portNumber, "/api/");
}).Start();
To limit the amount of calls that happen every second I added a dispatcher timer, that resets a bool after it has been used for a call, by my Event.
readonly System.Windows.Threading.DispatcherTimer packageIntervallTimer =
new System.Windows.Threading.DispatcherTimer();
bool readyForNewPackage = true;
private void ReadyForPackage(object sender, EventArgs e)
{
readyForNewPackage = true;
}
public async void StartListener(string ip, int port, string path)
{
packageIntervallTimer.Interval = TimeSpan.FromMilliseconds(50);
packageIntervallTimer.Tick += (s, e) => { Task.Run(() => ReadyForPackage(s, e)); };
packageIntervallTimer.Start();
Then I wrapped everything inside the while loop into an if condition based on the bool, the most important part was to have my "event EventHandler TcpReceived" in there:
// Grab packages sent in backend
while (client.State == WebSocketState.Open || client.State == WebSocketState.CloseSent)
{
if (readyForNewPackage == true)
{
readyForNewPackage = false;
try
{
....
TcpReceived(this, message);
....
}
catch
{
...
}
}
}
I added my TcpListenerLogic to the Eventhandler:
TcpReceived += TcpListenerLogic;
And my TcpListenerLogic now looked like this (names have been changed):
private async void TcpListenerLogic(object sender, string e)
{
try
{
dynamic results = JsonConvert.DeserializeObject<dynamic>(e);
if (results.test_id != null)
{
string testID = "";
if (results.test_id is JValue jValueTestId)
{
testID = jValueTestId.Value.ToString();
}
else if (results.test_id is string)
{
testID = results.test_id;
}
// Get properties for new object
string information = await CommunicationCommands.getJsonFromURL(
"http://" + ServerIP + ":" + ServerPort + "/api/" + testID );
if (information != null)
{
await App.Current.Dispatcher.BeginInvoke(new Action(() =>
{
// Create object out of the json string
TestStatus testStatus = new TestStatus();
testStatus.Deserialize(information);
if (CommunicationCommands.isNameAlreadyInCollection(testStatus.name) == false)
{
// Add new object to the list
CommunicationCommands.allFoundTests.Add(testStatus);
}
}));
{
}
catch (Exception exception)
{
....
}
}
Adding a new Thread to execute any step results in problems, so keep in mind that all this uses the thread created at the beginning for "StartListener"
I have two consumer on different queues in C# using RabbitMQ dot net library.
What i want:
Because of some business logic, I have to wait for some time in one consumer
so i have used Thread.Sleep() for that purpose
Problem
if I use Thread.Sleep in one event the second thread is also not paused
My code:
consumer.Received += (model, ea) =>
{
try
{
DRModel drModel = JsonConvert.DeserializeObject<DRModel>(Encoding.UTF8.GetString(ea.Body));
RMQReturnType type = ProcessSubmitSMS(drModel);
if (type == RMQReturnType.ACK)
channel.BasicAck(ea.DeliveryTag, false);
else
{
channel.BasicNack(ea.DeliveryTag, false, true);
Thread.Sleep(300000); // <=== SLEEP
}
}
catch (Exception ex)
{
channel.BasicNack(ea.DeliveryTag, false, true);
WriteLog(ControlChoice.ListError, "Exception: " + ex.Message + " | Stack Trace: " + ex.StackTrace.ToString() + " | [Consumer Event]");
}
};
It seems it is a good case for Mutex class, what you need is a conditional sleep in multithreading. Don't know exactly the logic that you need, but thy something like the code below:
public class Consumer
{
public event EventHandler Received;
public virtual void OnReceived()
{
Received?.Invoke(this, EventArgs.Empty);
}
}
class Program
{
static void Main(string[] args)
{
var mutex = new Mutex();
var consumer = new Consumer();
consumer.Received += (model, ea) =>
{
try
{
mutex.WaitOne();
var id = Guid.NewGuid().ToString();
Console.WriteLine($"Start mutex {id}");
Console.WriteLine($"Mutex finished {id}");
Console.WriteLine($"Start sleep {id}");
if ( new Random().Next(10000) % 2 == 0) // randomly sleep, that your condition
{
Thread.Sleep(3000); // <=== SLEEP
}
Console.WriteLine($"Sleep finished {id}");
}
catch (Exception ex)
{
mutex.ReleaseMutex(); // this is where you release, if something goes wrong
}
finally
{
mutex.ReleaseMutex();// always release it
}
};
Parallel.For(0, 10, t => //running 10 threads in parallel and stops all if the condition is true
{
consumer.OnReceived();
});
Console.ReadLine();
}
}
}
there is some logical error in my code whcih i understand.
In rabbitmq
i have created two consumer events on two different channels so i thought it will not be shared here i was wrong a connection was shared b/w channels so i explicly defin two connection for that.
as i understand
consumer block channel and channel block connection and connection is same in both events.
We have a cloud service using a worker role to process messages it receives from a Topic set up on Azure Service Bus.
The message itself seems to arrive intact and is usually received and processed correctly. In some instances however, the message seems to stop processing (Logging abruptly ends and no more references to the message being processed are seen in our WadLogsTable). From my research, this might be happening due to the worker role keeping its connection open and idle for longer than seconds. How would I go about preventing these long-to-process messages from being abandoned?
The code for our worker role is below.
public class WorkerRole : RoleEntryPoint
{
private static StandardKernel _kernel;
private readonly ManualResetEvent _completedEvent = new ManualResetEvent(false);
private BaseRepository<CallData> _callDataRepository;
private BaseRepository<CallLog> _callLogRepository;
private SubscriptionClient _client;
private NamespaceManager _nManager;
private OnMessageOptions _options;
private BaseRepository<Site> _siteRepository;
public override void Run()
{
try
{
List<CallInformation> callInfo;
Trace.WriteLine("Starting processing of messages");
// Initiates the message pump and callback is invoked for each message that is received, calling close on the client will stop the pump.
_client.OnMessage(message =>
{
// Process message from subscription.
Trace.TraceInformation("Call Received. Ready to process message ");
message.RenewLock();
callInfo = message.GetBody<List<CallInformation>>();
writeCallData(callInfo);
Trace.TraceInformation("Call Processed. Clearing from topic.");
}, _options);
}
catch (Exception e)
{
Trace.TraceInformation("Error: " + e.Message + "---" + e.StackTrace);
}
}
private void writeCallData(List<CallInformation> callList)
{
try
{
Trace.TraceInformation("Calls received: " + callList.Count);
foreach (var callInfo in callList)
{
Trace.TraceInformation("Unwrapping call...");
var call = callInfo.CallLog.Unwrap();
Trace.TraceInformation("Begin Processing: Local Call " + call.ID + " with " + callInfo.DataPoints.Length + " datapoints");
Trace.TraceInformation("Inserting Call...");
_callLogRepository.ExecuteSqlCommand(/*SNIP: Insert call*/);
Trace.TraceInformation("Call entry written. Now building datapoint list...");
var datapoints = callInfo.DataPoints.Select(datapoint => datapoint.Unwrap()).ToList();
Trace.TraceInformation("datapoint list constructed. Processing datapoints...");
foreach (var data in datapoints)
{
/*SNIP: Long running code. Insert our datapoints one at a time. Sometimes our messages die in the middle of this foreach. */
}
Trace.TraceInformation("All datapoints written for call with dependable ID " + call.Call_ID);
Trace.TraceInformation("Call Processed successfully.");
}
}
catch (Exception e)
{
Trace.TraceInformation("Call Processing Failed. " + e.Message);
}
}
public override bool OnStart()
{
try
{
var connectionString = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString");
_nManager = NamespaceManager.CreateFromConnectionString(connectionString);
_nManager.Settings.OperationTimeout = new TimeSpan(0,0,10,0);
var topic = new TopicDescription("MyTopic")
{
DuplicateDetectionHistoryTimeWindow = new TimeSpan(0, 0, 10, 0),
DefaultMessageTimeToLive = new TimeSpan(0, 0, 10, 0),
RequiresDuplicateDetection = true,
};
if (!_nManager.TopicExists("MyTopic"))
{
_nManager.CreateTopic(topic);
}
if (!_nManager.SubscriptionExists("MyTopic", "AllMessages"))
{
_nManager.CreateSubscription("MyTopic", "AllMessages");
}
_client = SubscriptionClient.CreateFromConnectionString(connectionString, "MyTopic", "AllMessages",
ReceiveMode.ReceiveAndDelete);
_options = new OnMessageOptions
{
AutoRenewTimeout = TimeSpan.FromMinutes(5),
};
_options.ExceptionReceived += LogErrors;
CreateKernel();
_callLogRepository.ExecuteSqlCommand(/*SNIP: Background processing*/);
}
catch (Exception e)
{
Trace.TraceInformation("Error on roleStart:" + e.Message + "---" + e.StackTrace);
}
return base.OnStart();
}
public override void OnStop()
{
// Close the connection to Service Bus Queue
_client.Close();
_completedEvent.Set();
}
void LogErrors(object sender, ExceptionReceivedEventArgs e)
{
if (e.Exception != null)
{
Trace.TraceInformation("Error: " + e.Exception.Message + "---" + e.Exception.StackTrace);
_client.Close();
}
}
public IKernel CreateKernel()
{
_kernel = new StandardKernel();
/*SNIP: Bind NInjectable repositories */
return _kernel;
}
}
Your Run method does not go on indefinitely. It should look like this:
public override void Run()
{
try
{
Trace.WriteLine("WorkerRole entrypoint called", "Information");
while (true)
{
// Add code here that runs in the role instance
}
}
catch (Exception e)
{
Trace.WriteLine("Exception during Run: " + e.ToString());
// Take other action as needed.
}
}
Taken from the docs:
The Run is considered the Main method for your application. Overriding
the Run method is not required; the default implementation never
returns. If you do override the Run method, your code should block
indefinitely. If the Run method returns, the role is automatically
recycled by raising the Stopping event and calling the OnStop method
so that your shutdown sequences may be executed before the role is
taken offline.
TheDude's response is very close to the correct answer! It turns out he's right that the run method needs to stay alive instead of returning immediately. With Azure Service Bus's message pump mechanism though, you can't place the _client.onMessage(...) inside a while loop, as this results in an error (The message pump has already been initialized).
What actually needs to happen is a a manual reset event needs to be created before the worker role begins executing, and then waited after the message pump code is executed. For documentation on ManualResetEvent, see https://msdn.microsoft.com/en-us/library/system.threading.manualresetevent(v=vs.110).aspx. Additionally, the process is described here: http://www.acousticguitar.pro/questions/607359/using-queueclient-onmessage-in-an-azure-worker-role
My final worker role class looks like this:
public class WorkerRole : RoleEntryPoint
{
private static StandardKernel _kernel;
private readonly ManualResetEvent _completedEvent = new ManualResetEvent(false);
private BaseRepository<CallLog> _callLogRepository;
private SubscriptionClient _client;
private MessagingFactory _mFact;
private NamespaceManager _nManager;
private OnMessageOptions _options;
public override void Run()
{
ManualResetEvent CompletedEvent = new ManualResetEvent(false);
try
{
CallInformation callInfo;
// Initiates the message pump and callback is invoked for each message that is received, calling close on the client will stop the pump.
_client.OnMessage(message =>
{
// Process message from subscription.
Trace.TraceInformation("Call Received. Ready to process message " + message.MessageId);
callInfo = message.GetBody<CallInformation>();
WriteCallData(callInfo);
Trace.TraceInformation("Call Processed. Clearing from topic.");
}, _options);
}
catch (Exception e)
{
Trace.TraceInformation("Error: " + e.Message + "---" + e.StackTrace);
}
CompletedEvent.WaitOne();
}
private void writeCallData(List<CallInformation> callList)
{
try
{
Trace.TraceInformation("Calls received: " + callList.Count);
foreach (var callInfo in callList)
{
Trace.TraceInformation("Unwrapping call...");
var call = callInfo.CallLog.Unwrap();
Trace.TraceInformation("Begin Processing: Local Call " + call.ID + " with " + callInfo.DataPoints.Length + " datapoints");
Trace.TraceInformation("Inserting Call...");
_callLogRepository.ExecuteSqlCommand(/*SNIP: Insert call*/);
Trace.TraceInformation("Call entry written. Now building datapoint list...");
var datapoints = callInfo.DataPoints.Select(datapoint => datapoint.Unwrap()).ToList();
Trace.TraceInformation("datapoint list constructed. Processing datapoints...");
foreach (var data in datapoints)
{
/*SNIP: Long running code. Insert our datapoints one at a time. Sometimes our messages die in the middle of this foreach. */
}
Trace.TraceInformation("All datapoints written for call with dependable ID " + call.Call_ID);
Trace.TraceInformation("Call Processed successfully.");
}
}
catch (Exception e)
{
Trace.TraceInformation("Call Processing Failed. " + e.Message);
}
}
public override bool OnStart()
{
try
{
var connectionString = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString");
_nManager = NamespaceManager.CreateFromConnectionString(connectionString);
_nManager.Settings.OperationTimeout = new TimeSpan(0,0,10,0);
var topic = new TopicDescription("MyTopic")
{
DuplicateDetectionHistoryTimeWindow = new TimeSpan(0, 0, 10, 0),
DefaultMessageTimeToLive = new TimeSpan(0, 0, 10, 0),
RequiresDuplicateDetection = true,
};
if (!_nManager.TopicExists("MyTopic"))
{
_nManager.CreateTopic(topic);
}
if (!_nManager.SubscriptionExists("MyTopic", "AllMessages"))
{
_nManager.CreateSubscription("MyTopic", "AllMessages");
}
_client = SubscriptionClient.CreateFromConnectionString(connectionString, "MyTopic", "AllMessages",
ReceiveMode.ReceiveAndDelete);
_options = new OnMessageOptions
{
AutoRenewTimeout = TimeSpan.FromMinutes(5),
};
_options.ExceptionReceived += LogErrors;
CreateKernel();
_callLogRepository.ExecuteSqlCommand(/*SNIP: Background processing*/);
}
catch (Exception e)
{
Trace.TraceInformation("Error on roleStart:" + e.Message + "---" + e.StackTrace);
}
return base.OnStart();
}
public override void OnStop()
{
// Close the connection to Service Bus Queue
_client.Close();
_completedEvent.Set();
}
void LogErrors(object sender, ExceptionReceivedEventArgs e)
{
if (e.Exception != null)
{
Trace.TraceInformation("Error: " + e.Exception.Message + "---" + e.Exception.StackTrace);
_client.Close();
}
}
public IKernel CreateKernel()
{
_kernel = new StandardKernel();
/*SNIP: Bind NInjectable repositories */
return _kernel;
}
}
You'll notice the presence of the ManualResetEvent and the invocation of WaitOne() at the end of my Run method. I hope someone finds this helpful!
So I need to update my WPF UI and here is the code for the same. This updates and element in the UI.
The commandPrompt object is created using a third party UI library noted here: http://www.codeproject.com/Articles/247280/WPF-Command-Prompt. I was testing this UI mockup.
And it throws the exception below:
The calling thread cannot access this object because a different
thread owns it.
private void CommandProcess(object sender, ConsoleReadLineEventArgs EventArgs)
{
string[] command = new string[EventArgs.Commands.Length];
for (int i = 0; i < EventArgs.Commands.Length; i++)
{
command[i] = EventArgs.Commands[i].ToLower();
}
if (command.Length > 0)
{
try
{
switch (command[0])
{
case "clear":
ProcessClear(command);
break;
case "demo":
ProcessParagraph(command);
break;
default:
new Task(() => { TextQuery(command); }).Start();
break;
}
}
catch (Exception ex)
{
WriteToConsole(new ConsoleWriteLineEventArgs("Console Error: \r" + ex.Message));
}
}
}
This is the TextQuery method. This uses the RestSharp.dll to communicate.
private void TextQuery(string[] command)
{
string APIQuery = ConvertStringArrayToString(command);
USBM usbm = new USBM("XXXXXX");
QueryResult results = usbm.Query(APIQuery);
if (results != null)
{
foreach (Pod pod in results.Pods)
{
Console.WriteLine(pod.Title);
if (pod.SubPods != null)
{
foreach (SubPod subPod in pod.SubPods)
{
EXCEPTION?? ====>WriteToConsole(new ConsoleWriteLineEventArgs("" + subPod.Title + "\n" + subPod.Plaintext));
}
}
}
}
}
This is the function that is used to write to the same custom console.
private void WriteToConsole(ConsoleWriteLineEventArgs e)
{
commandPrompt.OnConsoleWriteEvent(this, e); <=======EXCEPTION HERE
}
How can I let the threads share the data amongst them? I Google it but really couldn't get I it to work as I am new to async coding.
Thanks.
Since WriteToConsole calls commandPrompt, which accesses UI elements, that call should be made on the UI thread.
Since you use Task, you end up using another thread to invoke the UI from. That is not allowed and will give you the error you get.
You should call Dispatcher.Invoke or BeginInvoke to make the call on the UI thread:
Application.Current.Dispatcher.BeginInvoke
( new Action(() =>
WriteToConsole(new ConsoleWriteLineEventArgs(subPod.Title + "\n" + subPod.Plaintext)))
, DispatcherPriority.Background
);
I have a set of threaded classes that print different types of documents. The classes use inheritance to share common code. The class constructor requires file name and printer name arguments. A Print() method creates a new worker thread, waits for the worker thread to complete using Thread.Join(timeout) and calls Thread.Abort() on the worker thread if the Join times out. The worker thread starts an application that can open the specified file, causes the file to be sent to printer synchronously (usually using application's Print method) and exits. The worker thread's code is wrapped in a try{} ... catch{} block to deal with any unforeseen crashes of the external application. The catch block contains minimal cleanup and logging.
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 { }
}
}
I found my application non-responsive, with the main thread in a Sleep/Wait/Join at the Thread.Abort() line. I do not recall the status of the worker thread, but the logging that was supposed to be performed in the catch{} block did not take place. (I Attached to my process with VS2010 after I found it non-responsive).
I refer to the following Note from the Thread.Abort Method:
The thread that calls Abort might block if the thread that is being
aborted is in a protected region of code, such as a catch block,
finally block, or constrained execution region. If the thread that
calls Abort holds a lock that the aborted thread requires, a deadlock
can occur.
I believe I have a dead-locking issue because (1) it does not always happen, and (2) because of the Note on MSDN (above).
The Note appears to suggest that using try{} ... catch{} is NEVER safe inside a thread if the thread can be Abort()'ed. Is this true?
I do not see how I can avoid using Abort() in my scenario. Will using Thread.Interrupt() instead make any difference?
How to I fix the dead-locking issue I have?
BackgroundWorker does not work for me because I do not need progress reporting and, more importantly, it is possible that my worker thread will block indefinitely when it executes third party applications. For the same reason, I cannot ask my thread to terminate, but have one option only - to ruthlessly Abort() the worker thread.
Your mechanism using Thread.Abort() is not a good one. In fact, calling Thread.Abort() should be avoided.
The thread that calls Abort might block if the thread that is being
aborted is in a protected region of code, such as a catch block,
finally block, or constrained execution region. If the thread that
calls Abort holds a lock that the aborted thread requires, a deadlock
can occur. Ref.
Instead, use a BackgroundWorker which supports cancellation, progress reporting (and auto marshalling onto UI thread in completed event).
It looks to me like you are basically remote-controlling the PowerPoint application in order to print a PowerPoint document. Thus you could be subject to any dialog boxes that the application put up (or tried to put up) on the screen. If this whole thing is being run in the background (e.g. on a server), there may not be a user to dismiss any such dialogs, so that could explain part of the issue. My recommendation would be to look into third-party libraries that would allow you to load a PPT file and print it (or convert it to PDF and print that) without having to rely on the PowerPoint application. Then you wouldn't have to wait on an external app outside your control and you woudln't have to resort to forcefully aborting threads.
I think I found a solution by making the following changes:
Do not call Thread.Abort() if we know that the worker thread is executing a catch{} block (see protected volatile bool isPrinting below).
Use a separate thread to call Thread.Abort() and encourage a context switch with Sleep(0) (see private void AbortPrintWorker() below).
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() creates a separate thread to call Abort() on the worker thread. I believe this deals with the issue highlighted in the Note on Abort():
The thread that calls Abort might block if the thread that is being
aborted is in a protected region of code, such as a catch block,
finally block, or constrained execution region. If the thread that
calls Abort holds a lock that the aborted thread requires, a deadlock
can occur.
Is this correct?