I am struggling with an issue related to preventing resource access via the same thread (UI thread). Here is the scenario. The user clicks an object on the screen. Based on the selected object, a plot is drawn (and a member variable of the class is set to the selected object). Updates to the position of that selected object are received via a CollectionChanged event on that object. That handler uses Dispatcher.Invoke to marshall to the UI thread where additional updates to the plot take place. At any time, the user can "de-select" the object. When that happens, the class' member variable for the selected item is set to null. The problem is, an update can be received at any time. When this happens, it attempts to access data on the now null member variable. I have tried Lock, mutex, semaphore, SemiphoreSlim with ctor values of 1,1, etc.. However nothing seems to block the UI thread from attempting to access the member variable in both the event handler for the collection changed event and the user selection event handler (via a dependency object value changed event) from accessing the member variable at the same time.
DependencyObject value changed event handler (unrelated code removed):
try
{
mSemaphore.Wait();
if (mTrackHistory != null)
{
mTrackHistory.PositionData.CollectionChanged -=
PositionData_CollectionChanged;
}
mTrackHistory = e.NewValue as TrackHistoryInfo;
// If track history is null, don't do anything....
if (mTrackHistory != null)
{
Create initial plot
}
}
finally
{
mSemaphore.Release();
}
}
And the CollectionChanged event handler:
if (mDispatcher != Dispatcher.CurrentDispatcher)
{
mDispatcher.Invoke(() => PositionData_CollectionChanged(sender, e));
}
else
{
try
{
mSemaphore.Wait();
if (e.Action ==
System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
// Update the plot...
for (int idx = 0; idx < e.NewItems.Count; idx++)
{
(ECEF Position, DateTime Time) newInfo = ((ECEF Position, DateTime
Time))e.NewItems[idx];
// This is where it blows up attempting to access a null mTrackHistory
TimeSpan ts = newInfo.Time - mTrackHistory.StandupTime;
string stamp = $"{ts.Hours:D2}:{ts.Minutes:D2}:{ts.Seconds:D2}";
// Update plot with latest data
}
}
finally
{
mSemaphore.Release();
}
Any suggestions/direction on how to prevent this issue is greatly appreciated!
I would recommend something like the following pattern when doing any kind of background calculation:
This pattern removes any need for Dispatch.Invoke since everything except
DoWorkOnBackgroundThreadruns on the UI thread. You also do not have to worry about the selected object being set to null, since the background thread uses a local copy that is guaranteed to never change.You may also want to add functionality for cancelling the background work if the selected object changes, since the result will no longer be needed.