0

In my application I was using SystemEvents to add objects to an ObservableCollection (code shortened for this example)

public partial class App : Application
{
    private ObservableCollection<StateChanged> _messages = new ObservableCollection<StateChanged>();
    public ObservableCollection<StateChanged> messages { get { return _messages; } }

    protected override void OnStartup(StartupEventArgs e)
    {
        SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
    }

    private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
    {
        messages.Insert(0, new StateChanged(DateTime.Now, State.Logoff));
    }
}

Above code works without a problem.

Because I do not only have to handle SessionSwitch events, but also SessionEnding etc. I wrote a small class that should raise a 'unified' event for some of the SystemEvents (again shortened)

public class SystemEventArgs : EventArgs
{
    public State newState { get; set; }
}

public delegate void SystemEventHandler(object sender, SystemEventArgs e);

class SystemEventCollector
{
    public event SystemEventHandler SessionEvent;

    protected virtual void RaiseSystemEvent(SystemEventArgs e)
    {
        SystemEventHandler handler = this.SessionEvent;
        if (handler != null)
            handler(this, e);
    }

    public SystemEventCollector()
    {
        SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
    }

    protected void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
    {
        SystemEventArgs ea = new SystemEventArgs();
        ea.newState = State.Unknown;
        RaiseSystemEvent(ea);
    }
}

When I instanciate this class in my Application and subscribe to the SessionEvent, doing the same stuff, like this

public partial class App : Application
{
    private ObservableCollection<StateChanged> _messages = new ObservableCollection<StateChanged>();
    public ObservableCollection<StateChanged> messages { get { return _messages; } }
    private SystemEventCollector _sysEventCollector = new SystemEventCollector();

    protected override void OnStartup(StartupEventArgs e)
    {
         _sysEventCollector.SessionEvent += OnSessionEvent;
    }

    private void OnSessionEvent(object sender, SystemEventArgs e)
    {
        messages.Insert(0, new StateChanged(DateTime.Now, e.newState));
    }
}

The messages.Insert() call raises an exception

This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

I do understand that I can not update a GUI element from another thread than the one it was created on and have worked around this problem by using the extension method mentioned in this SO answer.

My question is to why this happens? My assumptions are that events are handled on the same thread as they are raised, so why is there a difference between handling the SessionSwitch event directly and my approach of raising an event when handling the SessionSwitch event? How are the SystemEvents different from my event? Do I have the correct solution to the problem? Is there a simpler solution?

Community
  • 1
  • 1
Andreas Wallner
  • 255
  • 2
  • 15
  • Your exception message is raised by the dispatcher, but none of your code indicates where the dispatcher is getting involved. Is the 'messages' collection the subject of a binding in a different part of the code? If so, there's your problem. – Gayot Fow Jun 08 '14 at 15:07
  • Yes, the `messages` collection is bound in the UI. But I do not see how that explains the difference in the two behaviours. As I said, I know that I'm not allowed to update a UI element from a non-UI thread. I just do not know where the difference between using the `SystemEvents.***` events and my `SystemEventCollector.SessionEvent` lies. Shouldn't they run on the same thread? – Andreas Wallner Jun 08 '14 at 15:45
  • Your handlers were created on the same thread, but that doesn't mean they will always be INVOKED on the same thread. Especially when events are raised out of the Win32 assemblies. Your 'quick-fix' is to put some marshalling into your SystemEvents_SessionSwitch method. I always use the ListCollectionView for that. – Gayot Fow Jun 08 '14 at 15:57

1 Answers1

1

From some testing it seems that the error lies in the non-working code is the instanciation of SystemEventCollector object. MS does all the necessary marshalling in their SessionEvents.*** handlers, this is why the first example works without problems. In the non-working code SystemEventCollector is no instanciated in the OnStartup function (which is called from the UI thread) but basically with the constructor. When marshalling from the SessionEvents is done, it goes to the wrong thread, leading to the problem.

Apart from my original solution, the problem can also be solved by instanciating the SystemEventCollector in the OnStartup function.

protected override void OnStartup(StartupEventArgs e)
{
    _sysEventCollector = new SystemEventCollector();
     _sysEventCollector.SessionEvent += OnSessionEvent;
}
Andreas Wallner
  • 255
  • 2
  • 15