使用 C# 和 WPF 的异步网络

本文关键字:异步 网络 WPF 使用 | 更新日期: 2023-09-27 18:36:07

我正在编写一个wpf应用程序来控制TCP上的嵌入式设备。写入设备很容易,但我在接收数据包时遇到问题。

我已经创建了一个 tcp NetworkStream,并使用 NetworkStream.BeginRead() 来侦听 TCP 数据。但是,一旦我编写了一个完整的数据包,我想更新我的 GUI 以反映新信息。似乎不允许我从异步回调线程执行此操作。

似乎有一种方法可以通过调度程序一次一个请求来执行此操作,但我几乎需要更新 GUI 上的每个控件。我不为每个数据包案例和每个 WPF 控件编写调度程序函数。如何从异步线程获取对 WPF 控件的完全访问权限?

编辑:下面是一个代码示例:

NetworkUnion union = (NetworkUnion)ar.AsyncState;
                union.BytesRead += tcpStream.EndRead(ar);
                if (union.BytesRead < union.TargetSize)
                    tcpStream.BeginRead(union.ByteArray, union.BytesRead, union.TargetSize - union.BytesRead, new AsyncCallback(ReadCommandCallback), union);
                else
                {
                    NetworkUnion payload = new NetworkUnion();
                    NetworkPacket pkt = (NetworkPacket)union.getArrayAsStruct();
                    // Respond to the packet
                    // Read the payload
                    payload.ByteArray = new byte[pkt.payloadSize];
                    tcpStream.Read(payload.ByteArray, 0, pkt.payloadSize);
                    // Determine what the payload is!
                    switch (pkt.code)
                    {
                        case (int)PacketCode.STATE:
                            payload.ObjectType = typeof(NetworkState);
                            NetworkState state = (NetworkState)payload.getArrayAsStruct();
                            Handle.fuelGauge.Value = Convert.ToDouble(state.mainFuel);
                            break;

当我尝试更新电量计时,我收到一个无效操作异常,调用线程无法访问,因为另一个线程拥有该对象

使用"Handle"变量是因为这是来自静态实用程序类

主要问题是,我真的必须更换每一行吗

Handle.fuelGauge.Value = Convert.ToDouble(state.mainFuel);

Handle.fuelGauge.Dispatcher.Invoke(
                                System.Windows.Threading.DispatcherPriority.Normal,
                                new Action(
                                    delegate()
                                    {
                                        Handle.fuelGauge.Value = Convert.ToDouble(state.mainFuel);
                                    }
                            ));

?这似乎过于重复。做这样的事情会更容易:

Handle.mainWindow.Dispather.Lock();
Handle.fuelGauge.Value = Convert.ToDouble(state.mainFuel);
change everything else...
Handle.mainWindow.Dispatcher.Unlock();

使用 C# 和 WPF 的异步网络

通常,在 .NET 中,需要将 UI 请求封送回主 UI 线程。这是在网络读取后尝试更新 UI 时的常见方案,因为在使用 Sockets 时,通常总是在某个其他(线程池)线程上接收数据。

因此,在接收数据的代码中,您应该将字节包装到一些有用的对象中,并将其封送回主 UI 线程上的入口点方法,然后该方法可以负责在现在正确的线程上分配 UI 更新。

有很多方法可以做到这一点,但也许比某些方法更灵活的方法是让套接字代码的调用方传入 SynchronizationContext 对象,然后可以使用该对象将接收到的数据封送到。

有关同步上下文的详细信息,请参阅此处

您无需回发每个套接字调用,只需将决定如何处理数据包的 swithc 语句移动到 UI 线程上的类中,并调用该方法即可。然后,该方法可以正常执行 UI 更新,因为来自它的所有调用都已正确封送。

不,您不需要像您所说的那样替换每一行。 像这样的事情怎么样:

NetworkUnion union = (NetworkUnion)ar.AsyncState;
            union.BytesRead += tcpStream.EndRead(ar);
            if (union.BytesRead < union.TargetSize)
                tcpStream.BeginRead(union.ByteArray, union.BytesRead, union.TargetSize - union.BytesRead, new AsyncCallback(ReadCommandCallback), union);
            else
            {
                NetworkUnion payload = new NetworkUnion();
                NetworkPacket pkt = (NetworkPacket)union.getArrayAsStruct();
                // Respond to the packet
                // Read the payload
                payload.ByteArray = new byte[pkt.payloadSize];
                tcpStream.Read(payload.ByteArray, 0, pkt.payloadSize);
                // Determine what the payload is!
                double yourvalue = new double(); 
                switch (pkt.code)
                {
                    case (int)PacketCode.STATE:
                        payload.ObjectType = typeof(NetworkState);
                        NetworkState state = (NetworkState)payload.getArrayAsStruct();
                        yourvalue = Convert.ToDouble(state.mainFuel);
                        break;
            /**
                Other Cases....
            */
         // After the switch 
                    Handle.fuelGauge.Dispatcher.Invoke(
                            System.Windows.Threading.DispatcherPriority.Normal,
                            new Action(
                                delegate()
                                {
                                    Handle.fuelGauge.Value = yourvalue;
                                }
                        ));