OliverM
Guest
|
My add-in provides for every workbook a task pane, containing book specific information. As the information is volatile and may change various times within a single second, I delegated the job to a backgroundworker.
The worker does in no way touch the object model and is safe to use. The worker can be started and stopped with a button on the task pane. Everything works like a charme except one scenario: If the add-in is unloaded (NOT unregistered) via the COM add-in dialog. The task pane unloads but none of its event fire. As a consequence there is no chance to kill the worker. I tried to work around the issue by consuming the AddinModule_AddinBeginShutdown and cancel the worker in the event handler. I get the event I call CancelAsync but the worker does not fire its Worker_Completed event. If I then reload the add-in and start the worker process by clicking the start/stop button in the task pane everything looks like normal to the point where I stop and start the worker again.
Suddenly I have 2 workers (and of course a hell of a mess!). The new one I created after reloading the add-in and the old one I was trying to kill in the AddinModule_AddinBeginShutdown event.
This scenario holds true if I shutdown Excel after unloading the add-in, then restart Excel, reload the add-in, start-stop-start the worker as described above.
This would not happen if Form_Closing or Form_Closed would fire on AddinBeginShutdown. In a normal scenario (Add-in stays loaded and Excel shuts down) I work around the issue by consuming the Workbook_Close event and cancel the worker there.
Any ideas how to solve that? |
|
OliverM
Guest
|
Update
This would not happen if Form_Closing or Form_Closed would fire on AddinBeginShutdown
This conclusion was dead wrong, I got the form's closing/closed events going with
private void CurrentInstance_AddinBeginShutdown(object sender, EventArgs e)
{
Close();
}
but still no luck. CancelAsync executes but the worker does not fire the Worker_Completed event.
This makes me wonder whether the moment the AddinBeginShutdown fires the message chain is already completely cut off. |
|
OliverM
Guest
|
I wrapped the Backgroundworker object in a helper class which holds a reference to the particular thread the worker is using. The helper class has a CancelOnAddinShutDown() method which is designed to interrupt the thread and if not successful, nuke it.
Consuming the CurrentInstance_AddinBeginShutdown event in the ADXTaskPane, I can now reliably terminate the backgroundworker thread.
private void CurrentInstance_AddinBeginShutdown(object sender, EventArgs e)
{
Logger.Info("Add-in is shutting down");
_enhancedBackgroundWorker.CancelOnAddinShutDown();
}
private void EnhancedBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Logger.Debug("Received the workers completed event");
}
public class EnhancedBackgroundWorker
{
private static readonly ILogger Logger = LogManager.GetLogger(typeof(LogManager));
private Thread _workerThread;
private BackgroundWorker _worker;
public EnhancedBackgroundWorker()
{
_worker = new BackgroundWorker {WorkerSupportsCancellation = true};
_worker.DoWork += _worker_DoWork;
_worker.RunWorkerCompleted += _worker_RunWorkerCompleted;
}
// For sake of convenience..
public event EventHandler<RunWorkerCompletedEventArgs> RunWorkerCompleted;
private void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Logger.Debug("Received internal worker completed event");
RunWorkerCompleted.Invoke(this, e);
}
private void _worker_DoWork(object sender, DoWorkEventArgs e)
{
_workerThread=Thread.CurrentThread;
Logger.Debug("Started Backgroundworker with thread id " + _workerThread.ManagedThreadId);
try
{
while (!_worker.CancellationPending)
{
// Do something useful...
Thread.Sleep(1000);
Logger.Debug("Done another loop");
}
}
catch (ThreadInterruptedException exception)
{
Logger.Debug("Thread id " + Thread.CurrentThread.ManagedThreadId + " has been interrupted");
}
catch (ThreadAbortException abortExc)
{
Logger.Debug("Thread id " + Thread.CurrentThread.ManagedThreadId + " has been aborted");
}
catch (Exception exc)
{
Logger.Debug("Thread id " + Thread.CurrentThread.ManagedThreadId + " Other exception: " + exc.Message);
}
}
public void CancelOnAddinShutDown()
{
if (!_worker.IsBusy) return;
// If in WaitSleepJoin state
_workerThread.Interrupt();
// Set reasonable timespan for thread to terminate
if (!_workerThread.Join(1000))
// Still alive? Nuke it!
_workerThread.Abort();
// Clean up
_worker.Dispose();
}
public void RunWorkerAsync(object arguments = null)
{
_worker.RunWorkerAsync(arguments);
}
public void CancelAsync()
{
_worker.CancelAsync();
}
}
2016-09-09 14:14:29,792 [4] DEBUG - Started Backgroundworker with thread id 4
2016-09-09 14:14:30,802 [4] DEBUG - Done another loop
2016-09-09 14:14:31,803 [4] DEBUG - Done another loop
2016-09-09 14:14:32,804 [4] DEBUG - Done another loop
2016-09-09 14:14:33,769 [1] INFO - Add-in is shutting down
2016-09-09 14:14:33,778 [4] DEBUG - Thread id 4 has been interrupted
2016-09-09 14:14:33,779 [5] DEBUG - Received internal worker completed event. Thread id: 4
2016-09-09 14:14:33,780 [5] DEBUG - Received EnhancedBackgroundWorker completed event |
|
Andrei Smolin
Add-in Express team
Posts: 18829
Joined: 2006-05-11
|
Hello Oliver,
Thank you very much for posting the solution!
Andrei Smolin
Add-in Express Team Leader |
|