David Wisniewski
Posts: 39
Joined: 2015-11-20
|
I'm not sure how to track down what is causing these exceptions to appear in the immediate window when I start debugging because it's not stopping outlook from starting or the addin from loading and not halting code exception. Can you please assist in how to find what is causing these execeptions so that I don't release a product that is causing errors behind the scenes?
This is the output I get in the Immediate Window, and I don't know how to find where they are being thrown:
Exception thrown: 'System.Runtime.InteropServices.COMException' in OutlookMacrosV2.dll
Exception thrown: 'System.ArgumentException' in OutlookMacrosV2.dll
Exception thrown: 'System.ArgumentException' in OutlookMacrosV2.dll
Exception thrown: 'System.ArgumentException' in OutlookMacrosV2.dll
Exception thrown: 'System.ArgumentException' in OutlookMacrosV2.dll
Exception thrown: 'System.ArgumentException' in OutlookMacrosV2.dll
Exception thrown: 'System.ArgumentException' in OutlookMacrosV2.dll
Exception thrown: 'System.ArgumentException' in OutlookMacrosV2.dll
|
|
David Wisniewski
Posts: 39
Joined: 2015-11-20
|
I figured it out, in VS if you press Ctrl+Alt+E it brings up the exception settings. I searched for the exception type and told it to break on those exceptions and then it took me straight to them the next time I ran in debug mode. All errors fixed and this is the first time in 8 years that I've had my addin error free.
One last question... how can I find out if I have COM objects that I'm not releasing? Because my addin is being used by a client that almost never ever turns off his laptop or closes excel, I'm afraid of accummulating too many resources by unreleased com objects. I've done my best to go through the code, but it's very extensive, and there are some parts where I'm honestly not 100% sure that I'm handling the comobjects properly. |
|
Andrei Smolin
Add-in Express team
Posts: 18830
Joined: 2006-05-11
|
|
Craig Callender
Posts: 21
Joined: 2021-07-06
|
FYI, according to https://docs.microsoft.com/en-us/dotnet/api/system.gc.waitforpendingfinalizers?view=net-6.0 (under Remarks), there is no guarantee that GC.WaitForPendingFinalizers() will ever return and may block forever. I've actually seen this consistently happen in a customer environment. The MSDN suggestion is it call it from another thread and time it out. We wrote the following class to do this for us:
public class GarbageCollectorDaemon
{
private static readonly SemaphoreSlim CollectionSemaphore = new SemaphoreSlim(1, 1);
private static readonly Lazy<Timer> LazyTimer = new Lazy<Timer>(CreateTimer, LazyThreadSafetyMode.ExecutionAndPublication);
// The interval to force garbage collection.
private static readonly TimeSpan Interval = TimeSpan.FromMinutes(45);
// How long to wait before seeing if the garbage collection thread is finished.
private static readonly TimeSpan DelayInterval = TimeSpan.FromMilliseconds(100);
// The maximum amount of time to wait for the garbage collection thread to finish.
private static readonly TimeSpan MaxWaitTime = TimeSpan.FromSeconds(3);
public static Timer Timer => LazyTimer.Value;
public static Timer Initialize()
{
return LazyTimer.Value;
}
/// <summary>
/// Safely calls Garbage collection by putting it on a separate thread and aborting that thread if it runs for too long.
/// </summary>
/// <returns>A boolean indicating if GC.Collect() was called.</returns>
public static async Task<bool> SafelyCollectAsync()
{
var collectionPerformed = false;
// Don't queue up requests, just enter if it's not being executed; otherwise skip.
if (await CollectionSemaphore.WaitAsync(0))
{
try
{
var threadTimer = new Stopwatch();
var gcThread = new Thread(UnsafeRelease);
try
{
gcThread.Start();
threadTimer.Start();
// GC.Collect() was called, so return true.
collectionPerformed = true;
while (gcThread.IsAlive && threadTimer.Elapsed < MaxWaitTime)
{
await Task.Delay(DelayInterval);
}
}
finally
{
threadTimer.Stop();
// If we're still alive when we get here, kill the thread.
if (gcThread.IsAlive)
{
gcThread.Abort();
}
}
}
finally
{
CollectionSemaphore.Release();
}
}
else
{
// Logger.Warn("Did not initialize garbage collection as it was already executing.");
}
return collectionPerformed;
}
private static Timer CreateTimer()
{
var timer = new Timer
{
Interval = Interval.TotalMilliseconds,
AutoReset = true,
Enabled = true
};
timer.Elapsed += OnTimedEventAsync;
return timer;
}
private static async void OnTimedEventAsync(object source, ElapsedEventArgs e)
{
await SafelyCollectAsync();
}
/// <summary>
/// The below code is not guaranteed to return and may block indefinitely. Ensure you call from a thread you can abort.
/// </summary>
private static void UnsafeRelease()
{
try
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
catch (ThreadAbortException exception)
{
// Logger.Error(exception, "Garbage collection was aborted by the controlling thread.");
}
catch(Exception exception)
{
// Logger.Error(exception, "There was an exception thrown during garbage collection.");
}
}
}
|
|
Andrei Smolin
Add-in Express team
Posts: 18830
Joined: 2006-05-11
|
Hello Craig,
Thank you very much!
Regards from Poland (CET),
Andrei Smolin
Add-in Express Team Leader |
|