2015-12-28 2 views
1

Я использую SynchronizationContext как средство для синхронизации с потоком GUI для WinForms и WPF. Недавно я столкнулся с проблемой со старым стилем асинхронных обратных вызовов:SynchronizationContext.Current в async обратном вызове

private void Button_Click(object sender, RoutedEventArgs e) 
    { 
     uiContext = SynchronizationContext.Current; 

     var cl = new TcpClient(); 
     cl.BeginConnect("127.0.0.1", 22222, ConnectedCallback, null); 
    } 
    public void ConnectedCallback(IAsyncResult result) 
    { 
     if (SynchronizationContext.Current != uiContext) 
      uiContext.Post(x => MyUIOperation(), null); 
     else 
      MyUIOperation(); 
    } 

    public void MyUIOperation() 
    { 
     Title = "Connected"; 
    } 

    private SynchronizationContext uiContext; 

Это будет сгенерировано исключение, потому что SynchronizationContext.Current в функции обратного вызова равно захваченные один, и, следовательно, операция UI выполняются в рабочем поток обратного вызова.

Использование этого же кода в WinForms работает так, как я ожидал.

На данный момент я обнимаю текущий ManagedThreadId и сравниваю его в обратном вызове. Каков правильный способ справиться с этим?

Update:

Я хотел бы добавить, что я модифицирование очень старый существующий класса, который в настоящее время использует следующую конструкцию:

if (control.InvokeRequired()) 
    control.BeginInvoke(SomeFunction); 
else 
    SomeFunction(); 

Я пытаюсь удалить зависимость WinForms, не имея большое влияние на клиентов этого класса. SomeFunction() вызывает события, поэтому, если я просто вызываю uiContext.Send() или uiContext.Post(), порядок выполнения изменяется, поскольку Post() всегда будет стоять в очереди вызова, а Send() всегда будет блокироваться.

Кроме того, это всего лишь очень маленький фрагмент кода, чтобы показать корень моей проблемы. В действительности функция, выполняющая Post(), может быть вызвана из основного потока.

Это нацеливание .NET 4,0

+0

Я бы сказал, создание службы, которая обеспечивает и дает вам контекст пользовательского интерфейса синхронизации является путь. Затем всегда добавляйте эту службу к любой части кода, в которой вы нуждаетесь, и вызываете сообщение в контексте без каких-либо проверок везде, где вы должны быть уверены, что он выполняется на переднем плане. –

+0

Можете ли вы разместить точное сообщение и трассировку стека за исключением, которое вы получаете? – Leandro

+0

«Вызывающий поток не может получить доступ к этому объекту, потому что ему принадлежит другой поток." – Night94

ответ

1

Потому что в моем случае, вызов MyUIOperation функции() нужно будет быть вызвана немедленно, если функция ConnectedCallback вызывается из основной нити.

Это означает, что вызов MyUIOperation() будет вызов блокировки, если ConnectedCallback вызывается в потоке пользовательского интерфейса, в отличии от неблокирующего, если он вызывается из другого потока. Этот детерминизм может вызвать другие проблемы в будущем.

Просто позвоните Send. Согласно this article, вызов Send просто вызовет делегата непосредственно, если он уже находится в потоке пользовательского интерфейса.

Кроме того, вы можете просто сделать Dispatcher.Invoke().

+0

См. мое обновление. Я понимаю, что я могу использовать Send или Post для синхронизации, и я согласен с тем, что исходный дизайн был плохим для начала. Однако для того, чтобы не вызывать цепную реакцию других плохих вещей, логика должна быть «Если я запущен в потоке пользовательского интерфейса, немедленно вызовите эту функцию, иначе очередь на вызов», и я бы хотел использовать SynchronizationContext и ничто не было в интерфейсе пользовательского интерфейса специфический – Night94

+0

Я понимаю. В этом случае я не вижу никакого хорошего решения, кроме обходного пути, который вы уже используете. Я не уверен, что «SynchronizationContext.Current» должен быть разным для каждого потока. В [documentation] (https://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext.current.aspx) говорится: «Это полезно для распространения контекста синхронизации из одного потока в другой», поэтому я 'd сказать, что он делится между потоками. Вы можете использовать 'Thread.CurrentThread.IsBackground' вместо' SynchronizationContext.Current! = UiContext', но я бы тоже не полагался на него. – Leandro

3

Оказалось, что в .NET 4.5 SynchronizationContext на самом деле отличается от функции обратного вызова, и оператор if будет оценивать значение true. Это было преднамеренное изменение, как обсуждалось here

WPF 4.0 had a performance optimization where it would 
    frequently reuse the same instance of the 
    DispatcherSynchronizationContext when preparing the 
    ExecutionContext for invoking a DispatcherOperation. This 
    had observable impacts on behavior. 
    1) Some task-parallel implementations check the reference 
     equality of the SynchronizationContext to determine if the 
     completion can be inlined - a significant performance win. 
    2) But, the ExecutionContext would flow the 
     SynchronizationContext which could result in the same 
     instance of the DispatcherSynchronizationContext being the 
     current SynchronizationContext on two different threads. 
     The continuations would then be inlined, resulting in code 
     running on the wrong thread. 
Смежные вопросы