I discovered an interesting issue while decorating an EventTopic subscription in my ModuleController for one of my modules. The decoration was like this:
[EventSubscription(EventTopicNames.MessageArrived, ThreadOption.UserInterface)]
public void OnMsgReceived(object sender, EventArgs<Msg> ev)
The event is fired on a non-UI thread, and I disovered this event handler was being called on that thread, NOT on the UserInterface thread as specified in the decoration. This decoration works fine in a View's Presenter class. But in my ModuleController class, it doesn't.
Well, I've looked into this further, and posted my findings at the CodePlex discussion group
http://www.codeplex.com/smartclient/Thread/View.aspx?ThreadId=8956
but I'll include my findings here as well:
When I subscribe to the event explicitly like this, my event handler does indeed get called on the UI thread:
public override void Run()
{
AddServices();
ExtendMenu();
ExtendToolStrip();
AddViews();
// Explicitly subscribe to this event topic
WorkItem.EventTopics{EventTopicNames.MessageArrived].AddSubscription(
this, "OnMsgReceived", WorkItem, ThreadOption.UserInterface);
}
But when I decorate my event handler like the following, (in ModuleController.cs) this event handler gets called actually on the same thread on which it was fired (which is NOT the UI thread).
[EventSubscription(EventTopicNames.MessageArrived, ThreadOption.UserInterface)]
public void OnMsgReceived(object sender, EventArgs<Msg> ev)
{
...
}
The relevant code I found while debugging (which I could only do when I explicitly subscribe to the event, although someone who knows how could do this by stepping through the CAB code that handles the decorations...)
If SynchronizationContext.Current is null at the time the subscription is being added, then the subscription’s syncContext property will NOT get set, and I believe this is likely the ultimate issue. You can see below the code snippet at subscription time. Below that is a code snippet at Fire execution time, and it clearly will simply call the event handler directly on the current thread if the syncContext property was not set at subscription time.
At Subscription time:
In EventBroker\Subscription.cs Ln 93
if (threadOption == ThreadOption.UserInterface)
{
// If there's a syncronization context (i.e. the WindowsFormsSynchronizationContext
// created to marshal back to the thread where a control was initially created
// in a particular thread), capture it to marshal back to it through the
// context, that basically goes through a Post/Send.
if (SynchronizationContext.Current != null)
{
syncContext = SynchronizationContext.Current;
}
}
At Fire time:
In EventBroker\Subscription.cs Ln 215
private void CallOnUserInterface(object sender, EventArgs e, List<Exception> exceptions)
{
Delegate handler = CreateSubscriptionDelegate();
if (handler != null)
{
if (syncContext != null)
{
syncContext.Send(delegate(object data)
{
try
{
((Delegate)data).DynamicInvoke(sender, e);
}
catch (TargetInvocationException ex)
{
exceptions.Add(ex.InnerException);
}
}, handler);
}
else
{
try
{
handler.DynamicInvoke(sender, e);
}
catch (TargetInvocationException ex)
{
exceptions.Add(ex.InnerException);
}
}
}
}