2

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()) by NotifyEntityChanged(pNotifyEventItem.ToArray()) or by System.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 dispatcher priority results

Question: What could be the reason for those delegates to remain pending for certain classes, while they work for other classes?

Philippe
  • 1,949
  • 4
  • 31
  • 57
  • Have you tried a different priority for your BeginInvoke? ApplicationIdle sounds risky to me. In addition, you can use the returned DispatcherOperation object to test if the dispatcher has not shut down as stated in Remarks section here: http://msdn.microsoft.com/en-us/library/cc647500(v=vs.110).aspx. – o_weisman Aug 20 '14 at 13:02
  • Yes I did, no matter what the priority is, the dispatcher is never being invoked. I've added the DispatcherOperations to a list for debugging purposes. Their state stays on pending, even after minutes, where the ones from the other class are being executed. – Philippe Aug 20 '14 at 13:34
  • 1
    You're calling Dispatcher.CurrentDispatcher from another thread, right? This sometimes can create a new Dispatcher that is not the one with access to the UI.. http://stackoverflow.com/questions/10448987/dispatcher-currentdispatcher-vs-application-current-dispatcher – Rodrigo Vedovato Aug 20 '14 at 14:26
  • @RodrigoVedovato Good call! For the above example, m_dispatcherOperationresults[0].Dispatcher ==m_dispatcherOperationresults[1].Dispatcher is true, while m_dispatcherOperationresults[0].Dispatcher == m_dispatcherOperationresults[4].Dispatcher is false. Now I have to figure out what has to be done to fix it. I still don't understand why it works for certain objects, but not for others. – Philippe Aug 20 '14 at 14:33
  • I have faced this before. Assuming that you're creating the ErgaenzungsfeldService class from the UI, you can create a readonly Dispatcher field that has the value of Dispatcher.CurrentDispatcher. This will give you the UI dispatcher. – Rodrigo Vedovato Aug 20 '14 at 14:37

1 Answers1

2

Just to make my comments "official". This might be caused by calling the Dispatcher.CurrentDispatcher from another thread. That can cause the application to create a new dispatcher that doesn't have any access to the UI.

Assuming that you're creating the ErgaenzungsfeldService class from the UI, you can create a readonly Dispatcher field that has the value of Dispatcher.CurrentDispatcher. This will give you the UI dispatcher.

Rodrigo Vedovato
  • 1,008
  • 6
  • 11
  • I'm going to accept your answer, as it put me on the right track to solve the issue. It turned out that my statement telling it works for some objects, but not for others was wrong! During the import process all dispatched events stay stuck in the queue of the dispatcher of the import thread and a manual event firing of the event from the UI-thread after the import triggered the correct behavior for the other objects :/ – Philippe Aug 21 '14 at 07:17