2010-04-13 139 views
2

第一个问题,所以大家好。如何从工作线程/类更新GUI线程/类?

我正在处理的要求是一个小型测试应用程序,它通过串行端口与外部设备进行通信。通信可能需要很长时间,设备可能会返回各种错误。

该设备是很好的抽象出它自己的类的GUI线程开始在自己的线程运行,并有通常的开启/关闭/读取数据/写入数据的基本功能。图形用户界面也相当简单 - 选择COM端口,从设备读取或错误打开,关闭,数据显示,允许修改和写回等

的问题仅仅是如何从更新设备类的GUI?设备处理的数据有几种不同的类型,所以我需要在GUI窗体/线程类和工作设备类/线程之间建立一个相对通用的桥接。在GUI到设备的方向上,一切工作正常[开始]在各种GUI生成事件上调用打开/关闭/读/写等调用。

我读过线程here (How to update GUI from another thread in C#?)其中假设,即GUI和工作线程都在同一个班。谷歌搜索引发了如何创建一个委托或如何创建经典的后台工作,但这根本不是我所需要的,尽管他们可能是解决方案的一部分。那么,是否有一个简单但通用的结构可以使用?

我的C#水平适中,并且我一直在编程我所有的工作生活给出一个线索,我会弄清楚(和回来后)提前...感谢您的帮助。

回答

6

您可以暴露你的UI类的公共方法,该设备类可以在后台线程与所有需要传递给UI的信息,请致电。该公共方法将在后台线程的上下文中执行,但由于它属于UI类,因此现在可以使用您已阅读过的任何调用封送技术。

因此,最简单的设计则是:

  • 方法添加到您的UI类(例如MyUIForm)呼吁像UpdateUI(),把你正在使用从设备传递数据的任何数据结构到您使用的用户界面。如果您希望稍后支持DI/IoC,并且让表单实现它,则可以在接口中声明该方法(例如IUIForm)。
  • 在线程A(UI线程)上,您的UI类创建设备类,初始化所有必需的设置并启动其后台线程。它也传递一个指向它自己的指针。
  • 上线程B,该装置收集的数据,并调用MyUIForm.UpdateUI()(或IUIForm.UpdateUI())。
  • UpdateUI确实InvokeBeginInvoke视情况而定。

请注意,它具有在UI类中封装所有UI和表示逻辑的优点。您的设备类现在可以专注于处理硬件。

更新:为了解决您的可扩展性的担忧 -

无论你的应用程序有多少增长多少UI类你有,你还是要越过使用的BeginInvoke针对特定UI类线程边界你想要更新。(这个UI类可能是一个特定的控件或特定的可视化树的根,它并不重要)主要原因是,如果你有多个UI线程,你必须确保任何UI的更新发生在线程上由于Windows消息传递和窗口的工作方式,这个特殊UI被创建。因此,跨越边界线程的实际逻辑应该被封装在UI层中。

您的设备类不应该关心哪些UI类和哪些线程需要更新。实际上,我个人会让设备对任何UI完全无知,并且只会暴露不同的UI类可以订阅的事件。

请注意,替代解决方案是使线程完全封装在设备类中,并使UI不知道是否存在缓动线程。但是,然后线程边界交叉成为设备类的责任,并且应该包含在其逻辑中,因此您不应该使用UI方式来穿越线程。这也意味着你的设备类被绑定到特定的UI线程。

+0

这可能是最简单的方法,它在一开始就运行良好,之前我已经使用过,但它的缺点是一旦GUI变得更大并且由更多类或UserControl组成(它不可避免地似乎发生)它不能很好地扩展。 也许我应该在我的问题中指出,我寻找一个通用解决方案的原因是我完全期望这个应用程序随着时间的推移而增长,并希望能够扩展。 – 0xDEADBEEF 2010-04-13 09:12:47

0

这是一个带有事件处理程序的版本。
它被简化,因此在窗体中没有UI控件,在SerialIoEventArgs类中没有任何属性。

  1. 将您的代码来更新自己的评论//更新UI
  2. 将您的代码从串行IO
  3. 读串行IO在注释//读取添加字段/属性下的UI SerialIoEventArgs类并使用OnReadCompleated方法填充它。
public class SerialIoForm : Form 
{ 
    private delegate void SerialIoResultHandlerDelegate(object sender, SerialIoEventArgs args); 
    private readonly SerialIoReader _serialIoReader; 
    private readonly SerialIoResultHandlerDelegate _serialIoResultHandler; 

    public SerialIoForm() 
    { 
     Load += SerialIoForm_Load; 
     _serialIoReader = new SerialIoReader(); 
     _serialIoReader.ReadCompleated += SerialIoResultHandler; 
     _serialIoResultHandler = SerialIoResultHandler; 
    } 

    private void SerialIoForm_Load(object sender, EventArgs e) 
    { 
     _serialIoReader.StartReading(); 
    } 
    private void SerialIoResultHandler(object sender, SerialIoEventArgs args) 
    { 
     if (InvokeRequired) 
     { 
      Invoke(_serialIoResultHandler, sender, args); 
      return; 
     } 
     // Update UI 
    } 
} 
public class SerialIoReader 
{ 
    public EventHandler ReadCompleated; 
    public void StartReading() 
    { 
     ThreadPool.QueueUserWorkItem(ReadWorker); 
    } 
    public void ReadWorker(object obj) 
    { 
     // Read from serial IO 

     OnReadCompleated(); 
    } 

    private void OnReadCompleated() 
    { 
     var readCompleated = ReadCompleated; 
     if (readCompleated == null) return; 
     readCompleated(this, new SerialIoEventArgs()); 
    } 
} 

public class SerialIoEventArgs : EventArgs 
{ 
} 
0

于是,经过基于上述回答一些调查研究,进一步搜索谷歌,并要求谁知道一些关于同事C#我选择的解决问题的方法如下。我仍然对评论,建议和改进感兴趣。

首先关于这个问题,这实际上是在这个意义上,GUI被控制的东西非常通用,即必须保持完全抽象的,通过一系列以它的响应GUI必须作出反应事件的一些进一步的细节。有几个不同的问题:

  1. 事件本身,具有不同的数据类型。随着程序的发展,事件将被添加,删除和更改。
  2. 如何桥接几个组成GUI的类(不同的UserControls)和抽象硬件的类。
  3. 所有类都可以产生并消耗事件,并​​且必须尽可能地解耦。
  4. 编译器应发现尽可能(例如发送一个数据类型的事件,但一个期望另一个comsumer)编码cockups

的这个第一部分是所述事件。由于GUI和设备可以引发多个事件,可能具有与它们相关的不同数据类型,因此事件调度程序非常方便。这必须是在这两个事件和数据通用的,所以:

// Define a type independent class to contain event data 
    public class EventArgs<T> : EventArgs 
    { 
    public EventArgs(T value) 
    { 
     m_value = value; 
    } 

    private T m_value; 

    public T Value 
    { 
     get { return m_value; } 
    } 
} 

// Create a type independent event handler to maintain a list of events. 
public static class EventDispatcher<TEvent> where TEvent : new() 
{ 
    static Dictionary<TEvent, EventHandler> Events = new Dictionary<TEvent, EventHandler>(); 

    // Add a new event to the list of events. 
    static public void CreateEvent(TEvent Event) 
    { 
     Events.Add(Event, new EventHandler((s, e) => 
     { 
      // Insert possible default action here, done every time the event is fired. 
     })); 
    } 

    // Add a subscriber to the given event, the Handler will be called when the event is triggered. 
    static public void Subscribe(TEvent Event, EventHandler Handler) 
    { 
     Events[Event] += Handler; 
    } 

    // Trigger the event. Call all handlers of this event. 
    static public void Fire(TEvent Event, object sender, EventArgs Data) 
    { 
     if (Events[Event] != null) 
      Events[Event](sender, Data); 

    } 
} 

现在我们需要一些事件,并从C世界的到来,我想枚举,所以我定义了一些事件,该GUI将提高:

public enum DEVICE_ACTION_REQUEST 
    { 
    LoadStuffFromXMLFile, 
    StoreStuffToDevice, 
    VerifyStuffOnDevice, 
    etc 
    } 

现在静态类此事件中,可以定义新的调度程序的范围(命名空间,典型值)以内的任何地方:

 public void Initialize() 
     { 
     foreach (DEVICE_ACTION_REQUEST Action in Enum.GetValues(typeof(DEVICE_ACTION_REQUEST))) 
      EventDispatcher<DEVICE_ACTION_REQUEST>.CreateEvent(Action); 
     } 

这为在连接每个事件的事件处理程序嗯。

并通过订阅这样的代码在消费设备对象的构造函数的事件消耗:

 public DeviceController() 
    { 
     EventDispatcher<DEVICE_ACTION_REQUEST>.Subscribe(DEVICE_ACTION_REQUEST.LoadAxisDefaults, (s, e) => 
      { 
       InControlThread.Invoke(this,() => 
       { 
        ReadConfigXML(s, (EventArgs<string>)e); 
       }); 
      }); 
    } 

凡InControlThread.Invoke是,仅包装invoke调用一个抽象类。

活动可以通过GUI简单地提出:

 private void buttonLoad_Click(object sender, EventArgs e) 
     { 
      string Filename = @"c:\test.xml"; 
      EventDispatcher<DEVICE_ACTION_REQUEST>.Fire(DEVICE_ACTION_REQUEST.LoadStuffFromXMLFile, sender, new EventArgs<string>(Filename)); 
     } 

这样做应该事件提高和消费类型不匹配(在此字符串文件名),编译器会抱怨的优势。

有许多增强功能可以做,但这是问题的严重性。正如我在评论中所说,我会感兴趣,特别是如果有任何明显的疏漏/缺陷或缺陷。希望这可以帮助某人。

+0

在我看来,你试图将C语义锚定到C#上。这不是生产力。你可以简单地将事件添加到ComSer类和.BeginInvoke(异步)他们和表单将订阅他们等现在你有这个可怕的事情与你自己的语义,没有人从C#后台本地来看任何理由。对不起,这是严厉的,没有冒犯的意思。 – 2010-04-19 09:15:26

+0

感谢您的评论。虽然你可以明显地以整个C#世界的名义提供评论,但我已经从你的建议中搜索了相关的观点,但我仍然不明白他们会如何提供帮助。 – 0xDEADBEEF 2010-04-19 10:52:42