Вывод из SynchonizationContext - PullRequest
       12

Вывод из SynchonizationContext

3 голосов
/ 15 октября 2010

Короче говоря, я реализовал класс, производный от SynchronizationContext, чтобы приложениям с графическим интерфейсом было проще получать события, возникающие в потоках, отличных от потока GUI. Я был бы очень признателен за комментарии по моей реализации. В частности, есть ли что-то, против чего вы бы порекомендовали, или это может вызвать проблемы, которые я не предвидел? Мои начальные тесты прошли успешно.

Длинная версия: В настоящее время я занимаюсь разработкой бизнес-уровня распределенной системы (WCF), которая использует обратные вызовы для передачи событий с сервера на клиенты. Одна из моих целей разработки - предоставить привязываемые бизнес-объекты (например, INotifyPropertyChanged / IEditableObject и т. Д.), Чтобы их было легко использовать на стороне клиента. В рамках этого я предоставляю реализацию интерфейса обратного вызова, который обрабатывает события по мере их поступления, обновляет бизнес-объекты, которые, в свою очередь, вызывают события изменения свойств. Поэтому мне нужно, чтобы эти события возникали в потоке графического интерфейса (чтобы избежать исключений между операциями). Отсюда моя попытка предоставить пользовательский SynchronizationContext, который используется классом, реализующим интерфейс обратного вызова, для распространения событий в поток GUI. Кроме того, я хочу, чтобы эта реализация была независимой от клиентской среды - например, приложение WinForms GUI или ConsoleApp или что-то еще. Другими словами, я не хочу предполагать, что статический SynchronizationContext.Current доступен. Отсюда мое использование ExecutionContext в качестве запасной стратегии.

public class ImplicitSynchronisationContext : SynchronizationContext

{

private readonly ExecutionContext m_ExecContext;
private readonly SynchronizationContext m_SyncContext;


public ImplicitSynchronisationContext()
{
    // Default to the current sync context if available.
    if (SynchronizationContext.Current != null)
    {
        m_SyncContext = SynchronizationContext.Current;
    }
    else
    {
        m_ExecContext = ExecutionContext.Capture();
    }
}


public override void Post(SendOrPostCallback d, object state)
{
    if (m_SyncContext != null)
    {
        m_SyncContext.Post(d, state);
    }
    else
    {
        ExecutionContext.Run(
            m_ExecContext.CreateCopy(),
            (object args) =>
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(this.Invoker), args);
            },
            new object[] { d, state });
    }
}
public override void Send(SendOrPostCallback d, object state)
{
    if (m_SyncContext != null)
    {
        m_SyncContext.Send(d, state);
    }
    else
    {
        ExecutionContext.Run(
            m_ExecContext.CreateCopy(),
            new ContextCallback(this.Invoker),
            new object[] { d, state });
    }
}


private void Invoker(object args)
{
    Debug.Assert(args != null);
    Debug.Assert(args is object[]);

    object[] parts = (object[])args;

    Debug.Assert(parts.Length == 2);
    Debug.Assert(parts[0] is SendOrPostCallback);

    SendOrPostCallback d = (parts[0] as SendOrPostCallback);

    d(parts[1]);
}

}

Ответы [ 4 ]

2 голосов
/ 15 октября 2010

К сожалению, вы написали что-то, что уже существует.Класс SynchronizationContext делает именно то, что вы делаете.Добавьте свойство в свой основной класс, как показано ниже:

    public static SynchronizationContext SynchronizationContext {
        get {
            if (SynchronizationContext.Current == null) {
                SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
            }
            return SynchronizationContext.Current;
        }
    }

Или используйте AsyncOperationManager.SynchronizationContext, оно делает то же самое.Желательно, конечно.

1 голос
/ 15 октября 2010

Я не вижу ничего технически неправильного в приведенном выше коде.

Однако, это сложнее, чем действительно необходимо.Нет реальной причины копировать ExecutionContext и запускать операции внутри него.Это происходит автоматически при вызове ThreadPool.QueueUserWorkItem.Для получения дополнительной информации см. Документы ExecutionContext :

В домене приложения весь контекст выполнения должен передаваться при каждой передаче потока.Эта ситуация возникает во время передач, выполняемых методом Thread.Start, большинством операций пула потоков и маршалинга потоков Windows Forms через насос сообщений Windows.

Лично я бы отказался от отслеживания ExecutionContext, если не будетреальная потребность в этом, и просто упростить это до:

public class ImplicitSynchronisationContext : SynchronizationContext
{
    private readonly SynchronizationContext m_SyncContext;

    public ImplicitSynchronisationContext()
    {
        // Default to the current sync context if available.
        m_SyncContext = SynchronizationContext.Current;
    }


    public override void Post(SendOrPostCallback d, object state)
    {
        if (m_SyncContext != null)
        {
            m_SyncContext.Post(d, state);
        }
        else
        {
            ThreadPool.QueueUserWorkItem(_ => d(state));
        }
    }

    public override void Send(SendOrPostCallback d, object state)
    {
        if (m_SyncContext != null)
        {
            m_SyncContext.Send(d, state);
        }
        else
        {
            d(state);
        }
    }
}
0 голосов
/ 18 октября 2010

Большое спасибо за отзывы всех.

Ответ Ханса Пассанта привел меня к развитию / изменению моего решения.

Просто, напомню, моя проблема была в основном, как получить асинхронные обратные вызовы от моегоСлужба WCF для распространения в потоке пользовательского интерфейса клиента (WinForms или WPF) без необходимости какой-либо работы со стороны разработчика клиента.

Я отбросил реализацию, предложенную выше, потому что она избыточна.Моя реализация контракта обратного вызова службы теперь просто имеет перегруженный конструктор, который принимает bool synchroniseCallbacks.Когда это правда, я сохраняю ссылку на AsyncOperationManager.SynchronizationContext.Когда события приходят из моей службы, я публикую или отправляю их с использованием этого контекста синхронизации.

Как указывал Ханс, преимущество использования контекста синхронизации, предоставляемого AsyncOperationManager, заключается в том, что он никогда не будет нулевым, а такжеПриложения с графическим интерфейсом, такие как WinForms и WPF, возвращают контекст синхронизации потока пользовательского интерфейса - проблема решена!

Приветствия!

0 голосов
/ 15 октября 2010

Я немного не уверен относительно вашей мотивации для написания этого класса.

Если вы используете WinForms или WPF, они предоставляют реализации, доступные с использованием SynchronizationContext.Current.

Есливы находитесь в консольном приложении, затем вы контролируете основной поток.Как вы общаетесь с этим потоком?

Если вы запускаете цикл сообщений Windows, возможно, вы используете WinForms или WPF.

Если вы ожидаете в очереди производителя / потребителя,ваш главный поток будет тем, кто потребляет события, поэтому по определению вы будете в главном потоке.

Ник

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...