我刚刚编写了一个小型的XBox 360无线控制器管理界面 绕过低杠杆SlimDX包装器库,为XBOX 360控制器提供简单的托管API。
在内部,该类每N ms轮询游戏手柄,并在检测到控制器基础状态发生变化时拍摄事件。
我正在经历一些与计时器相关的死胡同,它基本上是强迫在两个邪恶中较小的一个之间做出选择:
要么使我的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()技术...... 例如,如果所有计时器都有一个共享基类,我可以根据相关场景以某种方式将计时器作为依赖项注入。 有没有人成功处理过这类问题?
答案 0 :(得分:1)
轮询架构是唯一的选择吗?
无论如何,我个人会重构系统,以便外界可以订阅从控制器类触发的事件。
如果您希望控制器在正确的线程上下文中触发事件,那么我将为ISynchronizeInvoke接口添加一个属性,如果该属性在控制器内是非null,则使用该接口,否则只调用事件直接在控制器运行的线程上。
这将允许您将整个轮询事物放入一个每N毫秒唤醒一次的线程来完成其工作,甚至使用事件对象来等待事件(如果360控制器架构有这样的事情)。
答案 1 :(得分:0)
360控制器只能报告其当前状态。 没有其他方法可以在没有民意调查的情况下获得国家。 使用System.Threading.Timer或在我的视图中打开一个执行Thread.Sleep()的新线程实际上是相同的,它们都实现了无UI的计时器类的功能。
感谢您提及ISynchronizeInvoke,我已经搜索了更多内容,发现有人分享我的痛苦,甚至可能有一个解决方案: http://geekswithblogs.net/robp/archive/2008/03/28/why-doesnt-dispatcher-implement-isynchronizeinvoke.aspx
我会尝试他的代码并保持这个帖子发布...感谢您的提示!
答案 2 :(得分: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。
这意味着默认情况下,INSIDE 360 Controller类我不需要使用任何 UI特定的定时器......实际上,控制器的“默认”构造函数如下所示:
public XBox360GamePad(UserIndex controllerIndex) :
this(controllerIndex, (i,f) => new Timer(delegate { f(); }, null, i, i)) {}
我使用lambda函数来编写控制器类对定时器“service”的依赖性。
从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更有用/更好。
我很想听到喜欢这样的人的反馈意见/希望改善这种情况/对此感到反感等。
答案 3 :(得分:0)
您还可以查看并发和协调运行时,它是Microsoft Robotics Developers Studio的一部分(但很快就会成为独立产品)。
CCR是线程协调员,它允许MRDS完全支持诸如操纵机器人的操纵杆等操作,并在消息传递模型上操作。您可以设置从XBox操纵杆请求数据的计时器,后者又将数据发布到端口(队列)。然后设置在将数据发布到操纵杆端口以处理操作时被调用(接收器)的方法。提供了适用于WinForms的适配器,为WPF编写适配器并不困难。
这是一篇很棒的文章,说明如何在没有完整的MRDS的情况下使用CCR。 http://msdn.microsoft.com/en-us/magazine/cc163556.aspx
根据我的经验,一旦掌握了CCR的基础知识,多线程并发异步编程实际上变得很容易。
答案 4 :(得分:0)
@MattValerio:我知道CCR,但这并不符合这个法案,因为我打算将控制权放在googlecode上,而CCR不是框架或开源的一部分。