2

我刚刚编写了一个小型 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 Controller 类中使用 DispatcherTimer 我有一个工作组件可以直接更新 UI 而不会大惊小怪,但是现在我的整个控制器类都与 WPF 耦合,并且它不能在没有被使用的情况下使用依赖于 WPF(即在纯控制台应用程序中)

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

4

5 回答 5

1

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

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

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

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

于 2008-11-16T16:58:33.790 回答
0

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

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

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

于 2008-11-16T17:34:58.540 回答
0

所以...看来信息/代码@ 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 更有用/更好。

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

于 2008-11-16T19:03:32.917 回答
0

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

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

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

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

于 2008-11-17T02:20:27.073 回答
0

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

于 2008-11-17T08:46:54.830 回答