.NET 8套接字ReadAsync:为什么最后的回复恰好在取消时报告?

问题描述 投票:0回答:1

我需要等待超时的 UDP 广播回复,因此我尝试使用带有取消令牌的 ReadAsync 和 Threading.Timer 来发出取消信号。还有另外两台计算机对广播进行回复。如果我只使用“读取”,我会看到在传输后 0.05 秒和 0.27 秒收到回复。当使用 ReadAsync 并在广播后时间 T 取消时,将在 T 和 T-0.22 报告回复,时间为 T 0.5、1 和 1.5 秒。 换句话说,来自 Comp1 的回复始终报告为在取消时到达,而来自 Comp2 的回复始终报告为提前约 22 毫秒。

很奇怪的是,收到回复的时间取决于超时,知道为什么以及如何无论超时如何都能尽快收到回复吗?

我正在使用 .NET 8 Windows 应用程序,只是一个带有按钮的表单:

private async void button_Click(object sender, EventArgs e) {
    udp2 u = new udp2(local);
    List<udp2.Reply> rs = await u.Broadcast_as("hPC",TimeSpan.FromSeconds(waittime_sec)); 
}
public class udp2
{
    public readonly struct Reply {
        public readonly EndPoint From;
        public readonly DateTime When;
        public readonly string ReplyString;
        public Reply(EndPoint rrom, DateTime when, string reply) {
            From = rrom;
            When = when;
            ReplyString = reply;
        }
    }
    
    private readonly Socket m_socket;
    private readonly List<Reply> m_replies;
    public IList<Reply> Replies => m_replies;
    
    public udp2(IPEndPoint local) {
        m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        m_socket.Bind(local);
        m_socket.EnableBroadcast = true;
        m_replies = new List<Reply>();
    }
    public void Shutdown() { if (m_socket.IsBound) m_socket.Shutdown(SocketShutdown.Both); m_socket.Dispose();}
    public async Task<List<Reply>> Broadcast_as(string command, TimeSpan timeout) {
        List<Reply> replies = [];
        byte[] dg = Encoding.ASCII.GetBytes(command);
        IPEndPoint dst = new(IPAddress.Broadcast, 4321);
        
        m_socket.SendTo(dg,dst);// m_client.Send(dg,"255.255.255.255",4321);
        byte[] rq = new byte[1024];
        t_bs x = new t_bs();
        CancellationToken ct = x.GetToken();
        bool cancelled = false;
        Stopwatch sw = Stopwatch.StartNew();
        using (Timer t = new Timer(t_b,x,(int)timeout.TotalMilliseconds,Timeout.Infinite)) {
            do {
                IPEndPoint remote = new IPEndPoint(IPAddress.Any,0);
                EndPoint epremote = (EndPoint)remote;
                try { 
                    SocketReceiveFromResult rres = await m_socket.ReceiveFromAsync(rq,epremote, ct);
                
                    if (rres.ReceivedBytes>0) {
                        Reply re = new Reply(rres.RemoteEndPoint,DateTime.Now,Encoding.ASCII.GetString(rq,0,rres.ReceivedBytes));
                        replies.Add(re);
                    } 
                }
                catch (OperationCanceledException) { cancelled = true; sw.Stop();}
                
            } while (!cancelled);
        }
        Debug.WriteLine("cancelled after "+sw.ElapsedMilliseconds+" ms");
        return replies;
    }
    public List<Reply> Broadcast_syn(string command, TimeSpan timeout) {
        List<Reply> replies = new List<Reply>();
        byte[] dg = Encoding.ASCII.GetBytes(command);
        IPEndPoint dst = new IPEndPoint(IPAddress.Broadcast,4321);
        
        
        m_socket.SendTo(dg,dst);// m_client.Send(dg,"255.255.255.255",4321);
        byte[] rq = new byte[1024];
        Closocket x = new Closocket(m_socket);
        bool cancelled = false;
        using (Timer t = new Timer(t_clo,x,(int)timeout.TotalMilliseconds,System.Threading.Timeout.Infinite)) {
            do {
                IPEndPoint remote = new IPEndPoint(IPAddress.Any,0);
                EndPoint epremote = (EndPoint)remote;
                try { 
                    int recount = m_socket.ReceiveFrom(rq,ref epremote);
                
                    if (recount>0) {
                        Reply re = new Reply(epremote,DateTime.Now,Encoding.ASCII.GetString(rq,0,recount));
                        replies.Add(re);
                    }
                }
                catch (OperationCanceledException) { cancelled = true; }
                catch (Exception ex) { Debug.WriteLine(ex.Message); cancelled = true;}
                
            } while (!cancelled);
        }
        
        return replies;
    }
    private class t_bs {
        private int m_cnt;
        private CancellationTokenSource m_cts;
        public t_bs() {
            m_cts = new CancellationTokenSource();
            m_cnt = 0;
        }
        public CancellationToken GetToken() { return m_cts.Token; }
        public void ReqCancellation() { if (m_cnt==0) m_cts.Cancel(); m_cnt=1;}
    };
    private void t_b(Object? o) {
        if (o is t_bs tbs)
            tbs.ReqCancellation();
    }
}
c# sockets asynchronous async-await cancellation
1个回答
0
投票

这是因为任务取消是一个请求。

ReadAsync
代码在收到取消请求时不会立即退出。

要在 .Net C# 库之上实现心跳/udp ping 功能,以及它们所做的所有智能缓冲,您确实需要使用一个连续发送和接收的独立线程。 然后让该线程提供主程序可以使用的状态。

© www.soinside.com 2019 - 2024. All rights reserved.