I am intermittently getting a System.ObjectDisposedException after closing a Modal dialog that displays received BLE adverts. There is a higher chance of the error with increasing advert frequency.
The modal dialog is created by the app main form. In fact I have made it persist for the life of the app, in order to cross off one possible Dispose of an object. Code has been reduced to its most basic form to reproduce the problem and there are now more lines of debug asserts and output than actual code.
Here's the main form. Just one button to show the dialog modally...
using System.Diagnostics;
using TestBle;
namespace AdWatcherCrashTest
{
public partial class FormMain : Form
{
WatcherCrashTest watcherCrashTest;
public FormMain()
{
InitializeComponent();
Debug.WriteLine("Main Form Constructor: Create Dialog and assign to Main Form field...");
watcherCrashTest = new();
}
private void button1_Click(object sender, EventArgs e)
{
Debug.WriteLine("ShowDialog...");
DialogResult result = watcherCrashTest.ShowDialog(this);
Debug.Assert(watcherCrashTest != null);
Debug.WriteLine("ShowDialog returned " + result);
}
}
}
And here's the dialog that is displayed when the button is clicked...
using System.Diagnostics;
using Windows.Devices.Bluetooth.Advertisement;
namespace TestBle
{
public partial class WatcherCrashTest : Form
{
private BluetoothLEAdvertisementWatcher deviceWatcher;
public WatcherCrashTest()
{
Debug.WriteLine("Dialog constructor");
InitializeComponent();
deviceWatcher = new BluetoothLEAdvertisementWatcher();
}
private void WatcherCrashTest_Shown(object sender, EventArgs e)
{
Debug.WriteLine("Dialog Shown");
deviceWatcher.Received += deviceWatcher_Received;
deviceWatcher.Start();
}
private void WatcherCrashTest_FormClosing(object sender, FormClosingEventArgs e)
{
Debug.WriteLine("Dialog FormClosing");
if (deviceWatcher != null)
{
deviceWatcher.Stop();
deviceWatcher.Received -= deviceWatcher_Received;
// Adding the line below appears to avoid the exception but smells bad to me
// Application.DoEvents();
}
}
private void deviceWatcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
{
String BtDeviceDetails = "0x" + args.BluetoothAddress.ToString("X8") + ": " + args.Advertisement.LocalName;
Debug.WriteLine(BtDeviceDetails);
Debug.Assert(deviceWatcher != null);
Debug.Assert(this != null);
Debug.Assert(!this.IsDisposed);
Debug.Assert(!this.Disposing);
// Debug.Assert(false); // Uncomment to prove out Assert, once losing your mind.
// Getting intermittent 'Exception User-Unhandled' with whole Invoke statement highlighted.
// System.ObjectDisposedException: 'Cannot access a disposed object. ObjectDisposed_ObjectName_Name'
//
// Invoking with the Owner's method does not cause the exception
//this.Owner.Invoke(new Action(() =>
//
// So 'this' form is supposedly Disposed when the Invoke method is called.
// But the form is shown with ShowDialog(), so should not be disposed.
// What the hell???
//
this.Invoke(new Action(() =>
{
// The same exception is thrown, even if the line below is commented out.
listBox1.Items.Add(BtDeviceDetails);
}));
}
}
}
The exception is thrown when the dialog is closed using the standard 'cross' close button in the top right corner. Here is the debug output..
Main Form Constructor: Create Dialog and assign to Main Form field...
Dialog constructor
ShowDialog...
Dialog Shown
0x4EA52B5CA482:
0x78BDBC740D11:
0x6FE1A3309EF8:
0x69B9D65D8361:
0xE135AEE6F3FC:
0xCD322894FB49:
0xD0034B541726:
0x4A47B7552655:
0x78BDBC740D11:
0x1A56AD6F4DEF:
0x63F46825075B:
0xF71E0F339CFF:
Dialog FormClosing
ShowDialog returned Cancel
ShowDialog...
Dialog Shown
0xF71E0F339CFF:
0x6FE1A3309EF8:
0x4EA52B5CA482:
0xD0034B541726:
0x4A47B7552655:
0xC3B56E06B2D9:
0x6FE1A3309EF8:
Dialog FormClosing
Exception thrown: 'System.ObjectDisposedException' in System.Private.CoreLib.dll
An exception of type 'System.ObjectDisposedException' occurred in System.Private.CoreLib.dll but was not handled in user code
Cannot access a disposed object.
In the instance above, the exception was thrown the second time that the form was shown. This varies. Sometimes it is the first. Sometimes it takes several goes.
I have found two ways to prevent the exception, but neither smell right to me...
- Add an Application.DoEvents(); in FormClose.
- Use the Invoke method of the 'Owner' main form instead of the modal form to populate the listbox in the correct thread.
I don't understand why this (the dialog form) .Invoke causes an exception, since the form is not disposed of by a call to ShowDialog. Clearly the form isn't disposed of, since I can re-open the form by clicking the button on the main form once more. Furthermore, the previous entries in the listbox are still there.
Running a debug build, I get this error. I have yet to get the error on a release build.
Can anyone see why this is happening? Is my coding sense of smell appropriate, or is one of my fixes a fair solution?
I have about 30 years of Delphi experience, but unfortunately more like 30 minutes of c# .net experience (still learning). So it is quite possible that this error is a basic one, though I've spent hours trying to fix it, with plenty of web searches to no avail. Any help or suggestions appreciated.
Steve