计时器、UI 框架和不良耦合 - 有什么想法吗?

发布于 2024-07-09 04:11:50 字数 932 浏览 6 评论 0原文

我刚刚编写了一个小型 Xbox 360 无线控制器管理界面,基本上 包装低级 SlimDX 包装器库,并提供一个简单的、 XBOX 360 控制器的托管 API。

在内部,该类每 N 毫秒轮询一次游戏手柄,并在检测到控制器底层状态的变化时触发事件。

我遇到了一些计时器的死胡同,这基本上迫使我在两害相权取其轻:

  • 要么使我的 XBox360GamePad 类 UI 框架特定(即支持 WPF/WinForms 将在类中硬编码,并且该类必须引用这些框架...)

  • 使类完全与框架无关,但强制用户使用 Dispatcher.Invoke / Invoke() 调用来编写代码,以便能够根据生成的事件更新 UI。

如果我选择后一个选项(使代码与 UI 无关),那么我基本上使用“通用”System.Timers.Timer 或任何不依赖 UI 的计时器。 在这种情况下,我最终会从无法直接更新 UI 的线程生成/调用事件,即在 WPF 中,我必须通过(丑陋的)使用 Dispatcher.Invoke 来发出源自 360 控制器类的每个更新。

另一方面,如果我在 XBox 360 控制器类中使用 DispatcherTimer,我有一个可以直接更新 UI 的工作组件,无需大惊小怪,但现在我的整个控制器类已耦合到 WPF,并且无法在不被使用的情况下使用它。依赖于 WPF(即在纯控制台应用程序中)

我想要的是某种解决方案,它允许我既与框架无关,又可以更新 UI,而不必求助于各种 Dispatcher.Invoke() 技术... 例如,如果所有计时器都有一个共享基类,我可以根据相关场景以某种方式将计时器作为依赖项注入。 有没有人成功处理过此类问题?

I've just written a small XBox 360 Wireless Controller managed interface that basically
wraps around the low-lever SlimDX wrapper library and provides a easy, managed API for the XBOX 360 controller.

Internally, the class polls the gamepad every N ms, and shoots events as it detects changes in the underlying state of the controller.

I'm experiencing some what dead end with timers that is basiclly forcing to choose between the lesser of two evils:

  • Either make my XBox360GamePad class UI framework specific (i.e. support WPF/WinForms will be hard-coded in the class, and the class has to reference these frameworks...)

  • Make the class completely framework agnostic, but force the users to sprinkle their code with Dispatcher.Invoke / Invoke() calls to be able to update UI according to the events generated.

If I choose the latter option (of making the code UI agnostic), then I basically use the "generic" System.Timers.Timer or any timer that has no UI dependency.
In that case I end up having events generated/called from a thread that is incapable of directly updating the UI, i.e. in WPF, I would have to issue every update originated form the 360 controller class through the (ugly) use of Dispatcher.Invoke.

On the other hand, If I use DispatcherTimer inside the XBox 360 Controller class I have a working component that can update the UI directly with no fuss, but now my whole controller class is coupled to WPF, and it can't be used without being dependent on WPF (i.e. in a pure console app)

What I'm kind of looking is a some sort solution that would allow me to be both framework agnostic and also update UI without having to resort to all kinds of Dispatcher.Invoke() techniques...
If for example there was a shared base class for all timers, I could somehow inject the timer as a dependency according to the relevant scenario..
Has anyone ever dealt successfully with this sort of problem?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

記憶穿過時間隧道 2024-07-16 04:11:50

轮询架构是唯一的选择吗?

无论如何,我个人会重组系统,以便外界可以订阅从控制器类触发的事件。

如果您希望控制器在正确的线程上下文上触发事件,那么我将为 ISynchronizeInvoke 接口添加一个属性,如果该属性在控制器内非空,则使用该接口,否则直接调用事件,控制器运行的线程。

例如,这将允许您将整个轮询工作放入一个线程中,该线程每 N 毫秒唤醒一次以完成其工作,甚至使用事件对象来等待事件(如果 360 控制器架构有类似的东西)。

Is a polling architecture the only option?

In any case, personally I would restructure the system so that outside world can subscribe to events that is fired from the controller class.

If you want the controller to fire the events on the right thread context, then I would add a property for a ISynchronizeInvoke interface, and if this property is non-null inside the controller, use the interface, otherwise just invoke the events directly, on the thread the controller runs on.

This would for instance allow you make the entire polling thing into a thread that wakes up every N milliseconds to do its job, or even use event objects to just wait for events (if the 360 controller architecture has something like this).

温柔嚣张 2024-07-16 04:11:50

您还可以查看并发和协调运行时,它是 Microsoft Robotics Developers Studio 的一部分(但很快就会成为独立产品)。

CCR 是线程协调器,它允许 MRDS 完全支持诸如操纵杆之类的东西来驱动机器人,并在消息传递模型上运行。 您可以设置从 Xbox 操纵杆请求数据的计时器,然后 XBox 操纵杆将数据发送到端口(队列)。 然后,您设置在数据发送到操纵杆端口时调用的方法(接收器)以处理操作。 提供了 WinForms 的适配器,为 WPF 编写适配器并不困难。

这是一篇很棒的文章,说明了如何在没有成熟的 MRDS 的情况下使用 CCR。
http://msdn.microsoft.com/en-us/magazine/cc163556。 aspx

根据我的经验,一旦掌握了 CCR 的基础知识,进行多线程并发异步编程实际上变得很容易。

You could also take a look at the Concurrency and Coordination Runtime, which is part of Microsoft Robotics Developers Studio (but will be a stand-alone product soon).

The CCR is the thread coordinator that allows MRDS to fully supports things like joysticks driving robots around, and operates on a message-passing model. You can set up timers that request data from the XBox joystick, which in turn posts data to ports (queues). Then you set up methods that get called (receivers) when data is posted to the joystick port to handle the actions. Adapters for WinForms are provided and it wouldn't be difficult to write ones for WPF.

Here's an awesome article that illustrates how to use the CCR without the full-blown MRDS.
http://msdn.microsoft.com/en-us/magazine/cc163556.aspx

In my experience, once you get the basics of the CCR under your belt, doing multithreaded concurrent asynchronous programming actually becomes easy.

时光礼记 2024-07-16 04:11:50

@MattValerio:我了解 CCR,但这并不真正符合这里的要求,因为我计划将控制权放在 googlecode 上,而 CCR 不是框架或开源的一部分。

@MattValerio: I know about CCR, but this doesn't really fit the bill here since I plan to put the control on googlecode and the CCR isn't part of the framework or opensource.

独留℉清风醉 2024-07-16 04:11:50

360控制器只能报告其当前状态。
没有其他方法可以在不轮询的情况下获取状态。
在我看来,使用 System.Threading.Timer 或打开一个执行 Thread.Sleep() 的新线程实际上是相同的,它们都实现了无 UI 计时器类的功能。

感谢您提到 ISynchronizeInvoke,我在 google 上搜索了更多内容,发现有人分享我的痛苦,甚至可能有解决方案:
http://geekswithblogs.net/robp/archive/2008/03/28/why-doesnt-dispatcher-implement-isynchronizeinvoke.aspx

我会尝试他的代码并保持此线程发布... 谢谢你的提示!

The 360 controller can only report it's current state.
There is no other means to get the state without polling.
Using System.Threading.Timer or opening up a new Thread that does Thread.Sleep() is really the same in my view, both of them fulfill the functionality of a UI-less timer class.

Thanks for mentioning ISynchronizeInvoke, I've googled some more and found someone that shares my pain and might even have a solution:
http://geekswithblogs.net/robp/archive/2008/03/28/why-doesnt-dispatcher-implement-isynchronizeinvoke.aspx

I'll try his code and keep this thread posted... Thanks for the tip!

属性 2024-07-16 04:11:50

所以...
出现信息/代码@ http://geekswithblogs.net/robp/archive/2008/03/28/why-doesnt-dispatcher-implement-isynchronizeinvoke.aspx 确实提供了一个工作解决方案。
该代码完全独立于任何 UI,而仅依赖于 ISynchronizeInvoke 来实现正确的 UI/线程集成。

刚用过这个,我还是有点不愿意保持原样。

我的问题的要点是,每个事件调用函数看起来都是这样的:

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 类中我不需要使用任何
UI 特定计时器...本质上,我的控制器的“默认”构造函数如下所示:

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

我使用 lambda 函数来编码控制器类对计时器“服务”的依赖关系。

在 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 更有用/更好。

我很想听到喜欢这个/想要改进这个/对此感到厌恶等的人的反馈。

So...
It appears the information / code @ http://geekswithblogs.net/robp/archive/2008/03/28/why-doesnt-dispatcher-implement-isynchronizeinvoke.aspx does indeed provide a working solution.
The code is completely independent of any UI, and is instead only dependent on ISynchronizeInvoke for proper UI / Thread integration.

Having just used this, I'm still somewhat reluctant to leave it as is.

The gist of my problem is that every event invocation function looks something like this:

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 });
}

It's very annoying and confusing to write code this way, it looks, to me, like way too much fuss around just getting the damn thing to work. Basically I don't like having to wrap every event call with so much logic(!)

Therefore, I've opted for a different / additional strategy:
Basically, the constructor for the XBox360GamePad class now looks like this:

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

As you can see, it accepts a Func<int, Action, object> that is responsible for creating the timer and hooking it up.

This means that by default, INSIDE the 360 Controller class I don't need to use any
UI specific timers... In essence, my "default" constructor for the controller looks like this:

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

I use a lambda function to code the dependency of the controller class on a timer "service".

From WPF, I use the more general constructor to ensure that the control will be using the DispatcherTimer like this:

  _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;
  });

This way, I basically leave it up to the "user" to provide the timer implementation for the control, where the default implementation doesn't have to use any WPF or WinForms specific code.

I personally find this more useful / nicer design than using ISynchronizeInvoke.

I would love to hear feedback from people that like this / want to improve this / are disgusted by this etc.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文