Ответ 1
Непосредственная проблема
Ваша непосредственная проблема в том, что SynchronizationContext.Current
не устанавливается автоматически для WPF. Чтобы установить его, вам нужно будет сделать что-то подобное в коде TheUISync при работе в WPF:
var context = new DispatcherSynchronizationContext(
Application.Current.Dispatcher);
SynchronizationContext.SetSynchronizationContext(context);
UISync = context;
Более глубокая проблема
SynchronizationContext
привязан к поддержке COM + и предназначен для пересечения потоков. В WPF у вас не может быть диспетчера, который охватывает несколько потоков, поэтому один SynchronizationContext
не может пересекать потоки. Существует несколько сценариев, в которых SynchronizationContext
может переключиться на новый поток - в частности, все, что вызывает ExecutionContext.Run()
. Поэтому, если вы используете SynchronizationContext
для предоставления событий как для WinForms, так и для WPF-клиентов, вам нужно знать, что некоторые сценарии будут ломаться, например, веб-запрос к веб-службе или сайту, размещенному в том же процессе, будет проблемой.
Как обойти вокруг SynchronizationContext
В связи с этим я предлагаю использовать механизм WPF Dispatcher
исключительно для этой цели, даже с кодом WinForms. Вы создали одноэлементный класс "TheUISync", в котором хранится синхронизация, поэтому у вас есть способ подключиться к верхнему уровню приложения. Однако вы делаете это, вы можете добавить код, который создает добавление некоторого содержимого WPF в ваше приложение WinForms, так что Dispatcher
будет работать, а затем использовать новый механизм Dispatcher
, который я описываю ниже.
Использование диспетчера вместо SynchronizationContext
Механизм WPF Dispatcher
фактически устраняет необходимость в отдельном объекте SynchronizationContext
. Если у вас нет определенных сценариев взаимодействия с таким кодом обмена с объектами COM + или с пользовательскими интерфейсами WinForms, лучшим решением будет использовать Dispatcher
вместо SynchronizationContext
.
Это выглядит так:
public class Foo
{
public event EventHandler FooDoDoneEvent;
public void DoFoo()
{
//stuff
OnFooDoDone();
}
private void OnFooDoDone()
{
if(FooDoDoneEvent!=null)
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal, new Action(() =>
{
FooDoDoneEvent(this, new EventArgs());
}));
}
}
Обратите внимание, что вам больше не нужен объект TheUISync - WPF обрабатывает эту деталь для вас.
Если вам больше нравится более старый синтаксис delegate
, вы можете сделать это следующим образом:
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal, new Action(delegate
{
FooDoDoneEvent(this, new EventArgs());
}));
Несвязанная ошибка для исправления
Также обратите внимание, что в исходном коде есть ошибка, которая реплицируется здесь. Проблема в том, что FooDoneEvent может быть установлен равным нулю между временем, когда вызывается OnFooDoDone, и время, которое BeginInvoke
(или Post
в исходном коде) вызывает делегата. Исправление - это второй тест внутри делегата:
if(FooDoDoneEvent!=null)
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal, new Action(() =>
{
if(FooDoDoneEvent!=null)
FooDoDoneEvent(this, new EventArgs());
}));