Таймеры, рамки пользовательского интерфейса и плохая связь - есть идеи? - PullRequest
2 голосов
/ 16 ноября 2008

Я только что написал небольшой управляемый интерфейс XBox 360 Wireless Controller, который в основном оборачивает библиотеку-оболочку SlimDX с низким уровнем рычага и предоставляет простой, управляемый API для контроллера XBOX 360.

Внутренне, класс опрашивает геймпад каждые N мс и снимает события, когда он обнаруживает изменения в основном состоянии контроллера.

Я испытываю некоторые тупики с таймерами, которые вынуждены выбирать между меньшим из двух зол:

  • Либо сделайте мой фреймворк пользовательского интерфейса класса XBox360GamePad специфичным (т. Е. Поддержка WPF / WinForms будет жестко задана в классе, и класс должен ссылаться на эти фреймворки ...)

  • Сделайте класс полностью независимым от фреймворка, но заставьте пользователей посыпать свой код вызовами Dispatcher.Invoke / Invoke (), чтобы иметь возможность обновлять пользовательский интерфейс в соответствии с сгенерированными событиями.

Если я выберу последний вариант (сделать код независимым от пользовательского интерфейса), то я в основном использую «универсальный» System.Timers.Timer или любой таймер, который не зависит от пользовательского интерфейса. В этом случае я получаю события, сгенерированные / вызванные из потока, который не способен напрямую обновлять пользовательский интерфейс, то есть в WPF, мне придется выпускать каждое обновление, инициированное из класса контроллера 360, через (уродливое) использование Dispatcher.Invoke. .

С другой стороны, если я использую DispatcherTimer в классе контроллера XBox 360, у меня есть рабочий компонент, который может обновлять пользовательский интерфейс напрямую, без суеты, но теперь весь мой класс контроллера связан с WPF, и это не может быть используется без зависимости от WPF (т.е. в чисто консольном приложении)

То, на что я обращаю внимание, - это своего рода решение, которое позволило бы мне быть независимым от фреймворка и обновлять пользовательский интерфейс, не прибегая к всевозможным методам Dispatcher.Invoke () ... Если, например, существует общий базовый класс для всех таймеров, я мог бы как-то внедрить таймер как зависимость в соответствии с соответствующим сценарием. Кто-нибудь когда-нибудь успешно справлялся с такой проблемой?

Ответы [ 5 ]

1 голос
/ 16 ноября 2008

Является ли архитектура опроса единственным вариантом?

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

Если вы хотите, чтобы контроллер запускал события в контексте правого потока, я бы добавил свойство для интерфейса ISynchronizeInvoke, и если это свойство не равно нулю внутри контроллера, используйте интерфейс, в противном случае просто вызовите события непосредственно в потоке, на котором работает контроллер.

Это, например, позволит вам превратить весь опрос в поток, который просыпается каждые N миллисекунд, чтобы выполнить свою работу, или даже использовать объекты событий, чтобы просто ждать событий (если в архитектуре контроллера 360 есть что-то подобное).

0 голосов
/ 17 ноября 2008

@ MattValerio: Я знаю о CCR, но здесь это не совсем подходит, так как я планирую установить контроль над googlecode, а CCR не является частью фреймворка или открытого источника.

0 голосов
/ 17 ноября 2008

Вы также можете взглянуть на среду параллелизма и координации, которая является частью Microsoft Robotics Developers Studio (но скоро станет автономным продуктом).

CCR - это координатор потока, который позволяет MRDS полностью поддерживать такие вещи, как джойстики, управляющие роботами, и работает на модели передачи сообщений. Вы можете настроить таймеры, которые запрашивают данные у джойстика XBox, который, в свою очередь, отправляет данные в порты (очереди). Затем вы устанавливаете методы, которые вызываются (получатели), когда данные отправляются на порт джойстика для обработки действий. Адаптеры для WinForms предоставляются, и не составит труда написать их для WPF.

Вот отличная статья, которая иллюстрирует, как использовать CCR без полномасштабного MRDS. http://msdn.microsoft.com/en-us/magazine/cc163556.aspx

По моему опыту, как только вы усвоили основы CCR, выполнение многопоточного параллельного асинхронного программирования фактически становится легким.

0 голосов
/ 16 ноября 2008

Итак ... Похоже, информация / код @ http://geekswithblogs.net/robp/archive/2008/03/28/why-doesnt-dispatcher-implement-isynchronizeinvoke.aspx действительно обеспечивает рабочее решение. Код полностью независим от любого пользовательского интерфейса и вместо этого зависит только от ISynchronizeInvoke для правильной интеграции пользовательского интерфейса / потока.

Просто воспользовавшись этим, я все еще неохотно оставляю все как есть.

Суть моей проблемы в том, что каждая функция вызова события выглядит примерно так:

protected virtual void OnLeftThumbStickMove(ThumbStickEventArgs e)
{
  if (LeftThumbStickMove == null) return;

  if (_syncObj == null || !_syncObj.InvokeRequired)
    LeftThumbStickMove(this, e);
  else
    _syncObj.BeginInvoke(LeftThumbStickMove, new object[] { this, e });
}

Написание кода таким способом очень раздражает и сбивает с толку, мне кажется, это слишком много суеты вокруг того, чтобы заставить эту чертову штуку работать. По сути, мне не нравится, когда приходится оборачивать каждый вызов события такой большой логикой (!)

Поэтому я выбрал другую / дополнительную стратегию: По сути, конструктор для класса XBox360GamePad теперь выглядит следующим образом:

public XBox360GamePad(UserIndex controllerIndex, Func<int, Action, object> timerSetupAction)
{
  CurrentController = new Controller(controllerIndex);
  _timerState = timerSetupAction(10, UpdateState);
}

Как видите, он принимает Func, который отвечает за создание таймера и его подключение.

Это означает, что по умолчанию ВНУТРИ класса 360 Controller мне не нужно использовать какие-либо Таймеры, специфичные для пользовательского интерфейса ... По сути, мой конструктор по умолчанию для контроллера выглядит так:

public XBox360GamePad(UserIndex controllerIndex) : 
  this(controllerIndex, (i,f) => new Timer(delegate { f(); }, null, i, i)) {}

Я использую лямбда-функцию для кодирования зависимости класса контроллера от «службы» таймера.

Из WPF я использую более общий конструктор, чтобы гарантировать, что элемент управления будет использовать DispatcherTimer следующим образом:

  _gamePad = new XBox360GamePad(UserIndex.One, (i, f) => {
    var t = new DispatcherTimer(DispatcherPriority.Render) {Interval = new TimeSpan(0, 0, 0, 0, i) };
    t.Tick += delegate { f(); };
    t.Start();
    return t;
  });

Таким образом, я в основном оставляю это на усмотрение «пользователя», чтобы предоставить реализацию таймера для элемента управления, где реализация по умолчанию не должна использовать какой-либо специальный код WPF или WinForms.

Лично я считаю этот дизайн более полезным / приятным, чем использование ISynchronizeInvoke.

Мне бы очень хотелось услышать отзывы людей, которым это нравится / которые хотят это улучшить / мне это противно и т. Д.

0 голосов
/ 16 ноября 2008

Контроллер 360 может только сообщать о своем текущем состоянии. Других способов получить государство без голосования нет. Использование System.Threading.Timer или открытие нового потока, который выполняет Thread.Sleep (), на мой взгляд, на самом деле одинаковы, оба они выполняют функциональность класса таймера без пользовательского интерфейса.

Спасибо за упоминание ISynchronizeInvoke, я погуглил еще немного и нашел кого-то, кто разделяет мою боль и может даже иметь решение: http://geekswithblogs.net/robp/archive/2008/03/28/why-doesnt-dispatcher-implement-isynchronizeinvoke.aspx

Я попробую его код и оставлю эту тему в курсе ... Спасибо за подсказку!

...