No SynchronizationContext when calling Await in a another AppDomain
Asked Answered
S

1

3

I have successfully built a plugin mechanism where I can create UI controls in a separate AppDomain and display them as part of a Form in the main AppDomain.

These UI controls do their own data loading so when I open a form about 10 different plugins get created and each needs to load its data.

Again this all works fine if I do it synchronously but I would like to use the async/await pattern in each plugin. My refresh method looks like this:

protected async void RefreshData()
{ 
    _data = await LoadAsync(_taskId);  <= UI Thread     :)
    OnDataChanged();                   <= Worker Thread :( 
}

Now here starts the problem. When I enter this method I am on the main UI thread. But when the await is over I am on a worker thread so I get a cross thread exception in the OnDataChanged() method which updates the controls.

await should by default use the SynchronizationContext.Current for its continuation but since I am in an other AppDomain this happens to be NULL.

So my question is. How can I configure await to continue on the current thread, that is the UI thread?

I know I can grab a control and do Invoke() but I am also using the MVVM pattern and this is in the View Model so I don't have access to any controls there and all View Model -> View communications are done through data bindings.

Sharpe answered 14/4, 2016 at 15:4 Comment(3)
A little more code would help, I think, since personally I'm having a tough time visualizing what code is executing where. Also, stackoverflow.com/questions/20521286 may or may not offer some help (basic idea: use a custom SynchronizationContext that wraps, rather than relying on the default).Kaneshakang
I don't really know what other code to post. The Form is in the main appDomain, each control + view model is in a seperate AppDomain. This small peace of code is in the view model so it is running in this seperate AppDomain. The main UI thread comes along and starts the method but when await completes I am on a different thread. (all because I am in a seperate appDomain where SyncronizationContext is null)Sharpe
I tried what was suggjested in the article you pointed out before but that didn't work either: var syncCtx = SynchronizationContext.Current ?? new SynchronizationContext(); _data = await LoadAsync(_taskId); syncCtx.Send(OnDataChanged, null);Sharpe
S
0

I finally figured out how to get back to the UI-Thread from within a separate AppDomain, without having a handle to a control.

Since my view model is always instantiated on the UI thread, I simply grab the current dispatcher:

_dispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher

Now in my RefreshData method all I have to do is dispatch the action I want to perform after the await.

protected async void RefreshData()
{ 
    _data = await LoadAsync(_taskId);            <= UI Thread :)
    _dispatcher.Invoke(() => OnDataChanged());   <= UI Thread :)
}

This can of course be made more fancy, by encapsulating the dispatcher, etc.

The idea for this actually came from the: MVVM Light Toolkit

Sharpe answered 22/4, 2016 at 18:18 Comment(4)
Why don't you just capture the SynchronizationContext and then set it as current in the other AppDomain?Tenancy
Hi Stephen, having read a couple of your articles I was hoping you would comment on this one. I just tried your idea but I get the following error when I try to pass the SynchronizationContext from my main AppDomain to the plugin AppDomain: Type 'System.Windows.Forms.WindowsFormsSynchronizationContext' in Assembly 'System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' is not marked as serializable.Sharpe
Sorry, I know very little about AppDomains.Tenancy
No problem, thanks for the suggestion. I think however this is exactly the reason why there isn't any SynchronizationContext in the first place on the main thread when it enters the other appDomain. The context isn't serializeable and therefore cannot be reconstructed in the appDomain. It also doesn't derive from MarshalByRefObject so it cannot be used as a remote object.Sharpe

© 2022 - 2024 — McMap. All rights reserved.