I'm trying to fix a bug in an existing application, where in a single case, a dispatched event is not being executed.
In our application, there are several services implementing a common abstract class (AbstractService). The issue I've got occurs when a service saves an entity and has to raise a notification in the UI thread.
Relevant part of AbstractService:
public abstract class AbstractService<TEntity, TData, TInfo> : IDataProvider, IAbstractService
where TEntity : AbstractEntity, new()
where TData : AbstractData<TEntity>
where TInfo : IEntityInfo {
[...]
protected virtual void NotifyEntityChanged(NotifyEventItem<TInfo>[] pNotifyEventItems) { }
private void NotifyPersPlanChanged() {
if (PersPlanChanged != null)
PersPlanChanged(this, new NotifyEventArgs<IEntityInfo>(null, PersistanceState.Reset));
Debug.WriteLine(string.Format("{0} {1} Call {2} to NotifyPersPlanChanged",
DateTime.Now.ToLongTimeString(), GetType().Name, m_counterNotifyPersPlanChanged++));
}
private static int m_counterInternalFireNotification;
private void InternalFireNotification(List<NotifyEventItem<TInfo>> pNotifyEventItem) {
if (pNotifyEventItem == null || pNotifyEventItem.Count == 0)
return;
Debug.WriteLine(string.Format("{0} {1} Call {2} to InternalFireNotification",
DateTime.Now.ToLongTimeString(), GetType().Name, m_counterInternalFireNotification++));
// do not call the event handlers directly, because a transaction may be in progress and
// we want the event handlers to see the end results of the modifications.
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new HandleEntityChanged(NotifyEntityChanged), pNotifyEventItem.ToArray());
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new HandlePersPlanChanged(NotifyPersPlanChanged));
}
}
Every time a property of an entity has been modified through the service, the NotifyEntityChanged
is being called.
Here are two implementations of AbstractService
out of the ~20. The first one, is the one I'm having issues with, the second works. I'll describe further down what the issue is.
public class ErgaenzungsfeldService : AbstractServiceBasic<ErgaenzungsfeldEntity, ErgaenzungsfeldData>, IErgaenzungsfeldService {
[...]
[EventPublication(Constants.TopicErgaenzungsfeldChanged, PublicationScope.Global)]
public event EventHandler ErgaenzungsfeldChanged;
private static int m_counterNotifyEntityChanged;
protected override void NotifyEntityChanged(NotifyEventItem<IEntityInfoBasic>[] pNotifyEventItems) {
Debug.WriteLine(string.Format("{0} {1} Call {2} to NotifyEntityChanged",
DateTime.Now.ToLongTimeString(), GetType().Name, m_counterNotifyEntityChanged++));
base.NotifyEntityChanged(pNotifyEventItems);
if (ErgaenzungsfeldChanged != null) {
ErgaenzungsfeldChanged(this, new NotifyEventArgs<IEntityInfoBasic>(pNotifyEventItems));
}
}
}
public class DienstleistenderService : AbstractAdaLanguageDependentServiceVersion<DienstleistenderEntity, DienstleistenderData>, IDienstleistenderService {
[...]
[EventPublication(Constants.TopicDienstleistenderChanged, PublicationScope.Global)]
public event EventHandler DienstleistenderChanged;
private static int m_counterNotifyEntityChanged;
protected override void NotifyEntityChanged(NotifyEventItem<IEntityInfoVersion>[] pNotifyEventItem) {
Debug.WriteLine(string.Format("{0} {1} Call {2} to NotifyEntityChanged",
DateTime.Now.ToLongTimeString(), GetType().Name, m_counterNotifyEntityChanged++));
if (DienstleistenderChanged != null) {
DienstleistenderChanged(this, new NotifyEventArgs<IEntityInfoVersion>(pNotifyEventItem));
}
}
}
All services are instantiated like this:
WorkItem.RootWorkItem.Services.AddNew<ErgaenzungsfeldService, IErgaenzungsfeldService>();
WorkItem.RootWorkItem.Services.AddNew<DienstleistenderService, IDienstleistenderService>();
And accessed using either of these two ways:
WorkItem.Services.Get<IDienstleistenderService>()
[ServiceDependency]
public IErgaenzungsfeldService ErgaenzungsfeldService { get; set; }
Issue: ErgaenzungsfeldService.NotifyEntityChanged is not always being called. This only happens when importing data and NOT when using the application 'normally'. By normal I mean an ErgaenzungsfeldEntity can be modified in the application and saved. The same mechanism is being used and works.
Some observations:
- The import process is single-threaded. It does not run in the UI thread.
- DienstleistenderService.NotifyEntityChanged is being called correctly, when ErgaenzungsfeldService.NotifyEntityChanged is not
- Replacing
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new HandleEntityChanged(NotifyEntityChanged), pNotifyEventItem.ToArray())
byNotifyEntityChanged(pNotifyEventItem.ToArray())
or bySystem.Windows.Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new HandlePersPlanChanged(NotifyPersPlanChanged));
calls ErgaenzungsfeldService.NotifyEntityChanged correctly. As the UI gets notified from another thread, the application obviously throws exceptions later on. - If an ErgaenzungsfeldEntity is being saved through the UI, ErgaenzungsfeldService.NotifyEntityChanged is being called correctly.
Here's the output of the messages which have been added for debugging purposes to the different methods which helped me analyze the issue.
09:39:44 ErgaenzungsfeldService Call 0 to InternalFireNotification
09:39:44 ErgaenzungsfeldService Call 1 to InternalFireNotification
09:39:44 ErgaenzungsfeldService Call 2 to InternalFireNotification
09:39:44 ErgaenzungsfeldService Call 3 to InternalFireNotification
09:40:08 DienstleistenderService Call 0 to InternalFireNotification
09:40:08 DienstleistenderService Call 1 to InternalFireNotification
09:40:09 DienstleistenderService Call 2 to InternalFireNotification
[...]
09:40:14 DienstleistenderService Call 652 to InternalFireNotification
09:40:14 DienstleistenderService Call 653 to InternalFireNotification
09:40:14 DienstleistenderService Call 654 to InternalFireNotification
09:40:14 DienstleistenderService Call 655 to InternalFireNotification
09:40:14 AdaService Call 0 to InternalFireNotification
09:40:14 DienstleistenderService Call 656 to InternalFireNotification
09:40:14 DienstleistungService Call 0 to InternalFireNotification
09:40:14 AufbauorganisationService Call 0 to InternalFireNotification
09:40:14 AufbauorganisationService Call 1 to InternalFireNotification
09:40:14 AdaService Call 0 to NotifyPersPlanChanged
09:40:14 DienstleistenderService Call 0 to NotifyEntityChanged
09:40:16 DienstleistenderService Call 0 to NotifyPersPlanChanged
09:40:16 DienstleistungService Call 0 to NotifyPersPlanChanged
09:40:16 AufbauorganisationService Call 0 to NotifyPersPlanChanged
09:40:16 AufbauorganisationService Call 1 to NotifyPersPlanChanged
Question: What could cause the notification not to be executed as described?
[Edit] I've gained some more insight by storing the DispatcherOperation
:
private List<DispatcherOperation> m_dispatcherOperationresults = new List<DispatcherOperation>();
private void InternalFireNotification(List<NotifyEventItem<TInfo>> pNotifyEventItem) {
if (pNotifyEventItem == null || pNotifyEventItem.Count == 0)
return;
m_log.DebugFormat("InternalFireNotification called for {0}. TInfo type is {1}", GetType(), typeof(TInfo));
// do not call the event handlers directly, because a transaction may be in progress and
// we want the event handlers to see the end results of the modifications.
DispatcherOperation result = Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Send, new HandleEntityChanged(NotifyEntityChanged), pNotifyEventItem.ToArray());
m_dispatcherOperationresults.Add(result);
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new HandlePersPlanChanged(NotifyPersPlanChanged));
}
This made clear that the delegate executions are stuck in the execution queue. No matter if DispatcherPriority is set to ApplicationIdle (as it always was) or Send (which is the highest priority), they stay stuck for ErgaenzungsfeldService.
In this printscreen, items 0-3 have been triggered from a non-UI thread during import and stay pending, while item 4 has been triggered from the GUI thread (minutes later) and executed
Question: What could be the reason for those delegates to remain pending for certain classes, while they work for other classes?