C# 计时器、UI框架和糟糕的耦合-有什么想法吗?

C# 计时器、UI框架和糟糕的耦合-有什么想法吗?,c#,.net,wpf,timer,coupling,C#,.net,Wpf,Timer,Coupling,我刚刚编写了一个XBox 360无线控制器管理的小界面,基本上 环绕低层包装器库,为XBOX 360控制器提供一个易于管理的API 在内部,该类每N毫秒轮询一次gamepad,并在检测到控制器底层状态的变化时拍摄事件 我正在经历一些计时器的死胡同,这基本上迫使我在两个罪恶中的较小者之间做出选择: 或者使我的XBox360GamePad类UI框架特定(即支持WPF/WinForms将在类中硬编码,并且类必须引用这些框架…) 使类完全不依赖于框架,但强制用户在代码中加入Dispatcher.Inv

我刚刚编写了一个XBox 360无线控制器管理的小界面,基本上 环绕低层包装器库,为XBOX 360控制器提供一个易于管理的API

在内部,该类每N毫秒轮询一次gamepad,并在检测到控制器底层状态的变化时拍摄事件

我正在经历一些计时器的死胡同,这基本上迫使我在两个罪恶中的较小者之间做出选择:

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

  • 使类完全不依赖于框架,但强制用户在代码中加入Dispatcher.Invoke/Invoke()调用,以便能够根据生成的事件更新UI

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

另一方面,如果我在XBox 360控制器类中使用Dispatchermer,我有一个可以直接更新UI的工作组件,但现在我的整个控制器类都耦合到了WPF,如果不依赖WPF(即在纯控制台应用程序中)就无法使用它

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

轮询体系结构是唯一的选择吗

在任何情况下,我个人都会重新构造系统,以便外部世界可以订阅从控制器类触发的事件

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


例如,这将允许您将整个轮询事件放入一个线程中,该线程每N毫秒唤醒一次以执行其工作,或者甚至使用事件对象仅等待事件(如果360 controller体系结构具有类似的功能)。

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

感谢您提及ISynchronizeInvoke,我在谷歌上搜索了更多信息,发现有人与我分享痛苦,甚至可能有解决方案:

我将尝试他的代码并保持此帖子的发布。。。谢谢你的提示

所以。。。 看来信息/代码@确实提供了一个有效的解决方案。 该代码完全独立于任何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 });
}
public XBox360GamePad(UserIndex controllerIndex, Func<int, Action, object> timerSetupAction)
{
  CurrentController = new Controller(controllerIndex);
  _timerState = timerSetupAction(10, UpdateState);
}
public XBox360GamePad(UserIndex controllerIndex) : 
  this(controllerIndex, (i,f) => new Timer(delegate { f(); }, null, i, i)) {}
  _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;
  });
以这种方式编写代码是非常烦人和令人困惑的,在我看来,只是为了让这该死的东西正常工作而大惊小怪。基本上我不喜欢用这么多逻辑来包装每个事件调用(!)

因此,我选择了一种不同的/额外的策略: 基本上,XBox360GamePad类的构造函数现在如下所示:

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 });
}
public XBox360GamePad(UserIndex controllerIndex, Func<int, Action, object> timerSetupAction)
{
  CurrentController = new Controller(controllerIndex);
  _timerState = timerSetupAction(10, UpdateState);
}
public XBox360GamePad(UserIndex controllerIndex) : 
  this(controllerIndex, (i,f) => new Timer(delegate { f(); }, null, i, i)) {}
  _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;
  });
我使用lambda函数编码控制器类对计时器“服务”的依赖关系

在WPF中,我使用更通用的构造函数来确保控件将使用Dispatchermer,如下所示:

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 });
}
public XBox360GamePad(UserIndex controllerIndex, Func<int, Action, object> timerSetupAction)
{
  CurrentController = new Controller(controllerIndex);
  _timerState = timerSetupAction(10, UpdateState);
}
public XBox360GamePad(UserIndex controllerIndex) : 
  this(controllerIndex, (i,f) => new Timer(delegate { f(); }, null, i, i)) {}
  _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更有用/更好


我很想听到像这样的人的反馈,他们想改进这一点,对这一点很反感,等等。

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

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

这里有一篇很棒的文章,说明了如何在没有全面MRD的情况下使用CCR。


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

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