Cannot access a disposed object, on a static variable? - c#

I'm using the popup from the Maui.Community.Toolkit to display a waiting animation.
public static class WaitingService
{
private static WaitingPopup waitingPopup = new();
async public static Task ShowWaitingPopupAsync()
{
try
{
//2nd time this is called throws the exception
await Application.Current.MainPage.ShowPopupAsync(waitingPopup);
}
catch (Exception ex)
{
//cannot access a disposed object
await Application.Current.MainPage.DisplayAlert("Exception", ex.Message, "OK");
}
}
public static void HideWaitingPopup()
{
waitingPopup.Close();
}
}
The second time around that I call ShowWaitingPopupAsync() I get the disposed object exception. My understanding is that static classes/variables are persistent, and aren't disposed of until application exit, right?

Related

How to avoid Deadlock when using singleton Http Client in Winforms application

I have a legacy Windows Forms application that I am working on, I made some changes to the http client, I wanted to make it a singleton so that it could be reused throughout the application. It seems to be causing a deadlock.
I am going to paste all the code that I believe is involved below:
This is the calling code where the UI gets frozen, it never unfreezes.
private async void lbGroup_SelectedIndexChanged_1(object sender, EventArgs e)
{
int groupId = this.lbGroup.SelectedIndex + 1;
await LoadStores(groupId);
//The code below freezes the application
this.lbStore.DataSource = _stores;
this.txtSearch.Enabled = true;
this.lbStore.Enabled = true;
}
This is the LoadStores Method where the httpClient is used:
private async Task LoadStores(int group)
{
try
{
HttpResponseMessage res = await _httpClient.GetAsync("api/GetStoresByGroup/" + group.ToString());
res.EnsureSuccessStatusCode();
if (res.IsSuccessStatusCode)
{
var serializedStores = await res.Content.ReadAsStringAsync();
_stores = JsonConvert.DeserializeObject<IEnumerable<Store>>(serializedStores).Select(s => s.StoreName).ToList();
res.Content.Dispose();
}
}
catch (Exception ex)
{
ErrorLogger.LogError("Installation", $"Error getting stores list: {ex.Message}");
}
}
This is the Http Singleton Class:
public static class HttpClientSingleton
{
private static readonly HttpClient _instance;
static HttpClientSingleton()
{
_instance = new HttpClient();
_instance.BaseAddress = new Uri("https://www.i-city.co.za/");
_instance.DefaultRequestHeaders.Accept.Clear();
_instance.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
}
public static HttpClient Instance
{
get
{
return _instance;
}
}
}
This is the form constructor where the HttpClient gets initiliazed:
public partial class frmInstallationHelper : Form
{
private static string _configDir;
private static string _localConfigDir;
private static int _storeID;
private static Activation _activation;
private static HttpClient _httpClient = HttpClientSingleton.Instance;
private static IEnumerable<string> _stores;
private static IEnumerable<string> _franchisees;
private int _smsCounter;
If I wrap the http request in a using statement inside of the LoadStores method, the app runs fine, but I don't want to dispose of the http Client as that defeats the purpose of making it a singleton.
Update: Problem Found
After following #MongZhu's lead I replicated the program and confirmed that none of the above code was actually causing the deadlock. It was caused by another method that was triggered by the lbStore list Box onSelectChange event displayd below:
private void lbStore_SelectedIndexChanged_1(object sender, EventArgs e)
{
string store = this.lbStore.GetItemText(this.lbStore.SelectedItem);
LoadFranchisees(store).Wait();
this.lbFranchisees.DataSource = _franchisees;
}
The way I solved the problem was by changing it to look as follows:
private async void lbStore_SelectedIndexChanged_1(object sender, EventArgs e)
{
string store = this.lbStore.GetItemText(this.lbStore.SelectedItem);
await LoadFranchisees(store);
this.lbFranchisees.DataSource = _franchisees;
}
I was busy changing all the .wait() methods to async / await, and I must have forgotten this one.
The deadlock arises because you used Wait in a method which was triggered by an async opertaion. Unfortunately it was masked very good by the apparent hanging in the line of the initialization of the DataSource. But this initialization triggered the SelectedIndexChanged of the listbox which had the evil Wait call in it. Making this method async and await the result will evaporate the deadlock.
private async void lbStore_SelectedIndexChanged_1(object sender, EventArgs e)
{
string store = this.lbStore.GetItemText(this.lbStore.SelectedItem);
_franchisees = await LoadFranchisees(store);
this.lbFranchisees.DataSource = _franchisees;
}
I would suggest to return the stores directly from the method instead of using a class variable as transmitter. This way you would also avoid race conditions (to which methods that use class variables are very much prone) If you need it further you could store the returning value inside the _stores variable. But a loading method should rather return the results instead of secretely storing it somewhere hidden from the user of this method.
private async Task<List<Store>> LoadStores(int group)
{
try
{
HttpResponseMessage res = await _httpClient.GetAsync("api/GetStoresByGroup/" + group.ToString()))
res.EnsureSuccessStatusCode();
if (res.IsSuccessStatusCode)
{
var serializedStores = await res.Content.ReadAsStringAsync();
res.Content.Dispose();
return JsonConvert.DeserializeObject<IEnumerable<Store>>(serializedStores).Select(s => s.StoreName).ToList();
}
}
catch (Exception ex)
{
ErrorLogger.LogError("Installation", $"Error getting stores list: {ex.Message}");
}
}
You can await the result in the event:
private async void lbGroup_SelectedIndexChanged_1(object sender, EventArgs e)
{
int groupId = this.lbGroup.SelectedIndex + 1;
_stores = await LoadStores(groupId);
this.lbStore.DataSource = _stores;
this.txtSearch.Enabled = true;
this.lbStore.Enabled = true;
}
The same logic applies to the LoadFranchisees method, refactor it so that it returns the data. This makes your code much more understandable. Don't hide information from the reader of a method. It could be you in 6 Month trying to figure out what da heck you did there.... Be nice to your future self at least ;)

Should Dispose be called when rethrowing an unhandled exception?

Supposed I have a class like the below:
public class DisposableClass : IDisposable()
{
private readonly Timer timer;
DisposableClass()
{
this.timer = new Timer(s => cb(s), s, 1000, 1000);
}
Init()
{
try
{
// Do some initialization here that is not done in ctor.
}
catch (Exception)
{
// Log error.
throw;
}
finally
{
// Is this correct?
this.Dispose();
}
}
public void Dispose()
{
this.timer?.Dispose();
}
}
My question is whether the finally clause necessary (or should not have at all) in the above case, of for any non-constructor method when throwing an unhandled exception. Thanks.
EDIT:
In the answer, please address the issues depending on Init() being public,protected, private visibility levels.

Exceptions are not received in caller when using ASYNC

I am using DispatcherTimer to process a method at a specified interval of time
dispatcherTimer = new DispatcherTimer()
{
Interval = new TimeSpan(0, 0, 0, 1, 0)
};
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
Here is the dispatcherTimer_Tick method
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
try
{
Task.Run(() => MethodWithParameter(message));
}
catch (Exception ex)
{
}
}
Here I am calling MQTTPublisher which is a DLL reference.
private async static void MethodWithParameter(string message)
{
try
{
await MQTTPublisher.RunAsync(message);
}
catch (Exception Ex)
{
}
}
I am not able to catch the exceptions which are thrown in that DLL. How can I get exception to caller?
Definition of RunAsync - This is in separate dll.
public static async Task RunAsync(string message)
{
var mqttClient = factory.CreateMqttClient();
//This creates MqttFactory and send message to all subscribers
try
{
await mqttClient.ConnectAsync(options);
}
catch (Exception exception)
{
Console.WriteLine("### CONNECTING FAILED ###" + Environment.NewLine + exception);
throw exception;
}
}
And
Task<MqttClientConnectResult> ConnectAsync(IMqttClientOptions options)
This is the downside of using async void. Change your method to return async Task instead :
private async static Task MethodWithParameter(string message)
{
try
{
await MQTTPublisher.RunAsync(message);
}
catch (Exception Ex)
{
}
}
Based on: Async/Await - Best Practices in Asynchronous Programming
Async void methods have different error-handling semantics. When an exception is thrown out of an async Task or async Task method, that exception is captured and placed on the Task object. With async void methods, there is no Task object, so any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started.
And:
Figure 2 Exceptions from an Async Void Method Can’t Be Caught with Catch
private async void ThrowExceptionAsync()
{
throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
try
{
ThrowExceptionAsync();
}
catch (Exception)
{
// The exception is never caught here!
throw;
}
}

My Mutex is not working

private static bool Created;
private static System.Threading.Mutex PaintGuard = new System.Threading.Mutex(false, "MonkeysUncleBob", out Created);
//Function that is attached to each pages "LayoutUpdated" call.
private async void AnyPageLayoutUpdated(object sender, object e)
{
if (Created)
{
PaintGuard.WaitOne();
try
{
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
LCDDriver.ILI9488.PaintScreen(sender);
});
}
catch (Exception f)
{
}
finally
{
PaintGuard.ReleaseMutex();
}
}
}
The problem is that somehow multiple threads can enter into the code still.
I have verified this by using the debugger, and I can see multiple threads entering into the try before executing the finally.
I must be using it wrong.
await is not compatible with Mutex. You can use an async-compatible mutex like SemaphoreSlim or the AsyncLock that I have as part of my AsyncEx library.
However, if you need a named mutex, then you'll have to do something quite different. What that is depends on what exactly you're trying to do.
Update due to comments:
Since you're on the UI thread, there's no need to call into the dispatcher. You just need a SemaphoreSlim to keep them one-at-a-time:
private readonly SemaphoreSlim _mutex = new SemaphoreSlim(1);
//Function that is attached to each pages "LayoutUpdated" call.
private async void AnyPageLayoutUpdated(object sender, object e)
{
await _mutex.WaitAsync();
try
{
LCDDriver.ILI9488.PaintScreen(sender);
}
finally
{
_mutex.Release();
}
}

Disposing static objects c#

I have a static class which implements Excel-related functions (Class Library).
This dll is added as a reference to other applications, where I'm trying to use those functions.
I know that static objects are disposed when the main program terminates. Can I somehow dispose it before?
In my code, If I call CreateExcelDocument(excelFile), and instance of Excel is running in the background (I can see it in windows' processes manager). But, when I call DisposeExcelDocument(); the instance remains. How can I dispose it?
My goal is to open multiple Excel files, one by one, create graphs from the file currently open, and then close and move on to the next one. Is it even possible?
Here is the code:
public static class ExcelUtils
{
#region Private Members
private static Application m_excelApp;
private static Workbook m_excelWorkBook;
private static Worksheet m_excelWorkSheet;
#endregion Private Members
#region Properties
public static Worksheet ExcelWorkSheet
{
get { return m_excelWorkSheet; }
set { m_excelWorkSheet = value; }
}
#endregion Properties
#region Public Functions
public static void CreateExcelDocument(string excelFile)
{
try
{
m_excelApp = new Application();
m_excelApp.DisplayAlerts = false;
m_excelWorkBook = m_excelApp.Workbooks.Add(Type.Missing);
m_excelWorkSheet = (Worksheet)m_excelApp.ActiveSheet;
m_excelApp.DefaultSheetDirection = (int)Constants.xlLTR;
m_excelWorkSheet.DisplayRightToLeft = false;
if (excelFile.CompareTo("") != 0)
{
m_excelWorkBook = m_excelApp.Workbooks.Open(excelFile);
m_excelWorkSheet = (Worksheet)m_excelApp.Worksheets.get_Item(1);
m_excelWorkSheet.Columns.ClearFormats();
m_excelWorkSheet.Rows.ClearFormats();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return;
}
}
public static void DisposeExcelDocument()
{
try
{
m_excelApp.Quit();
ReleaseObject(m_excelWorkSheet);
ReleaseObject(m_excelWorkBook);
ReleaseObject(m_excelApp);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return;
}
}
public static void ReleaseObject(object currentObject)
{
try
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(currentObject);
currentObject = null;
}
catch (Exception ex)
{
currentObject = null;
Console.WriteLine(ex.ToString());
return;
}
finally
{
GC.Collect();
}
}
public static uint GetNumberOfRowsOrCols(string excelFile, bool getRows)
{
CreateExcelDocument(excelFile);
uint rowColNum = 0;
if (getRows)
rowColNum = (uint)m_excelWorkSheet.UsedRange.Rows.Count;
else
rowColNum = (uint)m_excelWorkSheet.UsedRange.Columns.Count;
DisposeExcelDocument();
return rowColNum;
}
#endregion Public Functions
}
First of all I agree with the comments regarding making this as non-static class.
But as far as your question is concerned, the Garbage Collector is not collecting the objects as you are not setting null to the class members, but just the local reference in ReleaseObject method.
To null the class members with least changes, will be to pass currentObject parameter to ReleaseObject method as ref, and have to use generics instead of object data type. So the method will become:
public static void ReleaseObject<T>(ref T currentObject) where T : class
and to call this method you will change like this:
ReleaseObject(ref m_excelWorkSheet);
You can leave the body of ReleaseObject method as it is, but I think calling GC.Collect() is not needed, and if you really have to, then call in from DisposeExcelDocument only once in the end, after you have called ReleaseObject for all the objects.

Categories