这里的第一个问题大家好。
我正在开发的需求是一个通过串行端口与外部设备通信的小型测试应用程序。通信可能需要很长时间,并且设备可能会返回各种错误。
设备被很好地抽象在自己的类中,GUI线程开始在自己的线程中运行,并具有通常的打开/关闭/读取数据/写入数据的基本功能。GUI也非常简单-选择COM端口,打开,关闭,显示从设备读取的数据或错误,允许修改和写回等。
问题很简单,如何从设备类更新GUI?设备需要处理几种不同类型的数据,所以我需要在GUI表单/线程类和工作设备类/线程之间建立一个相对通用的桥梁。在图形用户界面到设备的方向上,在各种图形用户界面生成的事件上,对于打开/关闭/读/写等BeginInvoke调用,一切都运行得很好。
我读过线程here (How to update GUI from another thread in C#?),其中假设图形用户界面和工作线程在同一个类中。Google搜索抛出了如何创建代理或如何创建经典的后台工作程序,但这并不是我所需要的,尽管它们可能是解决方案的一部分。那么,有没有一种简单但通用的结构可以使用呢?
我的C#水平是中等的,我的整个工作生涯都在编程,只要有线索我就会弄清楚(然后发回)……提前感谢您的帮助。
发布于 2010-04-13 15:20:20
您可以在UI类上公开一个公共方法,device类可以在后台线程上调用该方法,并提供传递给UI所需的所有信息。该公共方法将在后台线程的上下文中执行,但由于它属于UI类,因此您现在可以使用您已经阅读过的任何调用封送处理技术。
因此,最简单的设计是:
MyUIForm)添加一个名为UpdateUI()的方法,该方法采用您用来将数据从设备传递到您使用的UI的任何数据结构。如果你以后想要支持DI/IoC,你可以在一个接口(例如IUIForm)中声明这个方法,并让窗体实现它。MyUIForm.UpdateUI() (或者IUIForm.UpdateUI()).UpdateUI以appropriate.的身份执行Invoke或BeginInvoke
注意,这样做的另一个好处是将所有UI和表示逻辑封装在您的UI类中。你的设备类现在可以专注于处理硬件了。
更新:解决可伸缩性问题的-
无论您的应用程序增长了多少,您有多少UI类,您仍然希望使用要更新的特定UI类的BeginInvoke跨越线程边界。( UI类可能是特定的控件,也可能是特定可视化树的根,这并不重要)主要原因是如果您有多个UI线程,您必须确保任何UI的更新都发生在创建此特定UI的线程上,这取决于Windows消息传递和windows的工作方式。因此,跨越边界线程的实际逻辑应该封装在UI层中。
您的设备类不必关心哪些UI类以及需要在哪个线程上进行更新。事实上,就我个人而言,我会让设备完全不了解任何UI,只会公开不同UI类可以订阅的事件。
请注意,另一种解决方案是将线程完全封装在device类中,并使UI不知道是否存在后台线程。然而,线程边界的跨越就变成了device类的责任,应该包含在它的逻辑中,所以你不应该使用UI的方式来跨越线程。这也意味着你的设备类被绑定到特定的UI线程。
发布于 2010-04-13 16:40:05
这是一个带有事件处理程序的版本。
它经过了简化,因此窗体中没有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
{
}发布于 2010-04-19 17:03:50
因此,在基于上面的答案进行了一些研究之后,进一步的谷歌搜索并询问了一位对C#略知一二的同事,我选择的解决方案如下所示。我仍然对评论、建议和改进感兴趣。
首先是关于这个问题的更多细节,这个问题实际上是非常普遍的,因为GUI正在控制一些东西,这些东西必须通过一系列事件保持完全抽象,GUI必须对这些事件的响应做出反应。有几个明显的问题:
第一部分是事件。由于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
}现在,在EventDispatcher静态类的作用域(通常是命名空间)内的任何地方,都可以定义一个新的dispatcher:
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));
}这样做的好处是,如果事件引发类型和消费类型不匹配(这里是字符串Filename),编译器将发出抱怨。
可以进行许多增强,但这是问题的核心。正如我在评论中所说,我会很感兴趣,特别是如果有任何明显的遗漏/错误或不足之处。希望这对某些人有帮助。
https://stackoverflow.com/questions/2627606
复制相似问题