问题描述
最近我想了解更多关于内存分析的信息,所以我有一个简单的应用程序可以接收来自 UDP 的消息。现在我从这里使用标准的 Visual Studio 2019 内存分析器 https://memprofiler.com/。
从它的外观来看,我在这个应用程序中的内存使用量很小但不断增加,但我不知道我是否在做某事(内存分析的命名法对我来说很新,所以我通常不会打结我在看什么)。
所以归结为我的问题,分析器的问题数据向我展示了这一点
快照时间间隔大约为 10 分钟(但程序运行了一个小时,我每 10 分钟拍摄一次快照,所有过程都稳定,只是这部分在增加)
所以如果我理解正确的话,问题出在 $Udplistener.listenerWatcher@76-5 程序中,它是代码的一部分,如下所示:
member this.Start (udpReceiver : UdpClient) (onMessageReceived : byte array -> IBus -> unit) =
async {
try
let! receiveResult = udpReceiver.ReceiveAsync() |> Async.AwaitTask
let receiveBytes = receiveResult.Buffer
this.Logger.Debug
(sprintf "Received info %A from remote end poin Ip:%s Port:%i" receiveBytes (receiveResult.RemoteEndPoint.Address.ToString()) receiveResult.RemoteEndPoint.Port)
onMessageReceived receiveBytes bus
with
| :? ObjectdisposedException -> return ()
| :? SocketException -> return ()
| ex ->
match ex.InnerException :? ObjectdisposedException with
| true ->
this.Logger.Warn "Receiver is disposed or socked is closed,probably receiver restart"
return ()
| _ ->
this.Logger.Fatal(sprintf "Fatal error while starting listening,exception %A" ex)
raise (ex)
}
member this.Watcher (ip: IPAddress,port : int32) (onMessageReceived : byte array -> IBus -> unit) =
let rec listenerWatcher (udpReceiver : UdpClient) =
async {
try
do! this.Start udpReceiver onMessageReceived
return! listenerWatcher (udpReceiver)
with | ex ->
this.Logger.Fatal (sprintf "Udp listener watcher has beed stoped %A" ex)
return ()
}
(ip,port) |> this.CreateUdpReceiver |> listenerWatcher
UdpReceiver 在 watcher 启动时创建一次,它看起来像:
member private this.CreateUdpReceiver (ip: IPAddress,port : int32) : UdpClient =
try
let endpoint = IPEndPoint (ip,port)
let receivingClient = new UdpClient()
receivingClient.Client.Bind endpoint
receivingClient.Client.SetSocketoption (SocketoptionLevel.socket,SocketoptionName.ReuseAddress,true)
this.Logger.Debug (sprintf "Listener binded to Ip Adress:%s Port:%i" (endpoint.Address.ToString()) endpoint.Port)
UdpMediator.Instance.InvokeAddUdpReceiver receivingClient
receivingClient
with | ex ->
this.Logger.Fatal(sprintf "Fatal error while stoping udp clinet")
raise (ex)
而 InvokeAddUpdReceiver 只是将创建的接收器添加到外部,以便我们可以在不需要或需要刷新时处理他。
所以基本上我无法确定泄漏的来源,对我来说它看起来不错,但我可能对某些事情视而不见。
解决方法
我在您的代码中看到的一个潜在问题来源是在 listenerWatcher
函数中实现递归调用的方式。你有:
let rec listenerWatcher (udpReceiver : UdpClient) = async {
try
do! this.Start udpReceiver onMessageReceived
return! listenerWatcher (udpReceiver)
with ex ->
this.Logger.Fatal (sprintf "Udp listener watcher has beed stoped %A" ex) }
这里的问题是使用 return!
的递归调用位于 try .. with
块内。这可以防止异步运行时将其视为尾递归调用。它必须保留一个为异常处理分配的处理程序,因此它为每个递归调用增加一个分配的对象。
您应该能够通过将异常处理移到递归循环之外来解决此问题:
let rec listenerWatcher (udpReceiver : UdpClient) = async {
do! this.Start udpReceiver onMessageReceived
return! listenerWatcher (udpReceiver) }
let startListenerWatcher udpReceiver = async {
try
return! listenerWatcher udpReceiver
with ex ->
this.Logger.Fatal (sprintf "Udp listener watcher has beed stoped %A" ex) }
或者,假设您不需要在递归调用中保留任何状态,您可以只使用 while
循环而不是递归:
let listenerWatcher (udpReceiver : UdpClient) = async {
try
while true do
do! this.Start udpReceiver onMessageReceived
with ex ->
this.Logger.Fatal (sprintf "Udp listener watcher has beed stoped %A" ex) }