【问题标题】:Part of console app stops working after a few minutes C#. Please copy and paste this console app code for yourself控制台应用程序的一部分在 C# 几分钟后停止工作。请自行复制并粘贴此控制台应用程序代码
【发布时间】:2018-08-20 20:04:15
【问题描述】:

下面的应用程序在输入 IP 地址后“运行”大约 2 分钟(不确定确切时间),然后“停止”运行。 “停止”是指当我在调试器运行 10 分钟后暂停调试器时,它卡在这条线上:

知道为什么吗?

编辑:

您可以将以下代码复制并粘贴到控制台应用程序中,它将运行。这需要几分钟,经过几次迭代后,它(通常)会卡在消息 161 上。

编辑: 此应用程序的发送部分停止工作。但对 UDP 的侦听按预期继续。请参阅已接受的答案,了解为什么会发生这种情况。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Net;
using System.Net.Sockets;


namespace UDPTest2
{
    class Program
    {
        static string ip;
        static UDPSender sender;
        static UDPListener listener;
        private static bool _quitFlag = false;

        static void Main(string[] args)
        {
            //Console.WriteLine("Enter '1' to listen for UDP messages or '2' to send UDP messages, or 3 for both sending and receiving");
            int choice = 3;
            //if (Int32.TryParse(Console.ReadLine(), out choice))
            //{
                int port = 10001;
                Console.WriteLine("Enter YOUR IP address (include periods)");
                ip = Console.ReadLine();
                switch (choice)
                {
                    case 3: // send and receive from same terminal
                        Task taskReceive = new Task(() =>
                        {
                            listener = new UDPListener(ip, port);
                            listener.StartListener();
                        }, TaskCreationOptions.LongRunning);
                        taskReceive.Start();

                        sender = new UDPSender(ip, port);
                        sender.SendUDPContinuously(100);

                        break;
                    case 4:
                        break;
                    default:
                        Console.WriteLine("the input entered was not understood" + Environment.NewLine);
                        break;
                }
            //}
            //else
            //{
                //Console.WriteLine("Invalid integer input");
            //}
            //Console.WriteLine("press any key to exit");
        }
      Console.ReadLine();
    }

    public class UDPSender
    {
        private Socket s;
        private static byte sequenceNumber = 255;
        private IPAddress toIpAddress;
        private int portNumber;
        private byte lastOctet;         // first part of message byte array
        public UDPSender(string ipAddress, int portNumber)
        {
            s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,
                ProtocolType.Udp);
            toIpAddress = IPAddress.Parse(ipAddress);
            lastOctet = toIpAddress.GetAddressBytes()[3];
            this.portNumber = portNumber;
        }

        public void SendUDPOnce()
        {
            byte[] sendbuf = new byte[4];
            BuildByteMessage(lastOctet, Encoding.ASCII.GetBytes("PF"), sendbuf); // I can refactor this to just take a CommandParameters.BytesAndCommandEnum
            IPEndPoint ep = new IPEndPoint(toIpAddress, portNumber);
            s.SendTo(sendbuf, ep);
            //Console.WriteLine("Sender: Message sent to the broadcast address: " + toIpAddress + Environment.NewLine + "Sender: contents (first byte is IP_Octet, second is sequenceNumber): " + string.Join(",", sendbuf));
        }

        public void SendUDPContinuously(int delayMiliseconds)
        {
            Timer timerstuff = new Timer(sate => SendUDPOnce(), null, 0, delayMiliseconds);
        }

        private void BuildByteMessage(byte lastOctet, byte[] parameterCommand, byte[] destination)
        {
            byte sequenceNum = CreateNextSequenceByte();
            destination[0] = lastOctet;
            destination[1] = sequenceNum;
            Buffer.BlockCopy(parameterCommand, 0, destination, 2, parameterCommand.Length);
        }

        public byte CreateNextSequenceByte()
        {
            if (sequenceNumber != 255)
            {
                sequenceNumber++;
            }
            else
            {
                sequenceNumber = 0;
            }
            return sequenceNumber;
        }
    }

}

namespace UDPTest2
{
    public class UDPListener
    {
        string ipAddress;
        int listenPort;

        public UDPListener(string ipAddress, int listenPort)
        {
            this.ipAddress = ipAddress;
            this.listenPort = listenPort;
        }

        public void StartListener()
        {
            bool done = false;

            UdpClient listener = new UdpClient(listenPort);
            IPEndPoint groupEP = new IPEndPoint(IPAddress.Parse(ipAddress), listenPort);

            try
            {
                while (!done)
                {
                    //Console.WriteLine("Receiver: Waiting for broadcast...");

                    //First byte of the received message is the ASCII Mesage type flag: Q=Data, M=Metric, F=Fault. Second byte is the U8 sequence number. Third byte on is the actual data. 
                    byte[] bytes = listener.Receive(ref groupEP);
                    byte[] data = new byte[bytes.Length - 2];
                    Buffer.BlockCopy(bytes, 2, data, 0, bytes.Length - 2);
                    Console.WriteLine("Receiver: broadcast from {0} :\nReceiver: {1}\nReceiver: contents of each byte: " + string.Join(", ", bytes),
                        groupEP.ToString(),
                        Encoding.ASCII.GetString(bytes)); // Or Encoding.ASCII.GetString(bytes, 0, bytes.Length));
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            finally
            {
                listener.Close();
            }
        }
    }
}

【问题讨论】:

  • 如果在控制台中按下一个键会发生什么?
  • 按随机键没有任何反应,但 Ctr+C 仍然按预期工作(这是我在更改 _quitFlag 变量之前检查的内容)。我注意到如果我在 UDPListener 类的 while 循环中取消注释长的 Console.WriteLine,应用程序会更快退出。
  • 你到底是什么意思,当然它会在Console.ReadKey 等待,这就是应该发生的事情。如果您能提供MCVE,那将会很有帮助
  • 完成。以上可以复制到控制台应用程序以重现问题。我试图在没有无关紧要的细节的情况下尽可能地接近原作。这可以粘贴到一个文件中(“Program.cs”)
  • 你输入的是哪个选项?

标签: c# multithreading timer task console-application


【解决方案1】:

Timer timerstuff 是一个局部变量。它可以在某个时候被 GC 处理掉。让它成为一个领域,使其永久化并保持活力。

public class UDPSender
{
    private Timer _timerstuff;

    ...

    public void SendUDPContinuously(int delayMiliseconds)
    {
        _timerstuff = new Timer(sate => SendUDPOnce(), null, 0, delayMiliseconds);
    }                           

    ...
}

请参阅:Automatic memory management and garbage collection: Releasing Memory (Microsoft Docs)

【讨论】:

  • 这正是正在发生的事情。但这意味着所有需要保持活动状态的线程、任务、计时器等都必须是字段或全局变量,而不是局部变量;这个说法正确吗?
  • 每个必须保持活动的对象都必须是可访问的。它很可能是一个局部变量,只要调用方法是活动的(如Program.Main)并且该变量在范围内。它也可以是活动的 lambda 表达式中捕获的变量。
  • @Oliver 我注意到的一件事是 Task taskReceive = new Task(() ... 对象,即使这不是一个字段,当代码超出了 switch 语句的范围。为什么这是规则的例外,但计时器不是。也许这只是随机机会?
  • 这取决于编译器的工作方式。即使变量不在 C# 术语的范围内,它也可能保存在堆栈或寄存器中。但这可能会在未来的编译器版本中改变。只要任务正在运行,也可能将引用保存在任务池中。我肯定会扩大它的范围。
猜你喜欢
  • 1970-01-01
  • 2015-11-10
  • 1970-01-01
  • 2011-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多