I'm just profiling a UWP application running on a device trying to diagnose an observed memory leak.
Using snapshots, I've observed that I have a growing number of Task objects, which seem to be created from an internal component which raises logging events.
Here is a screenshot of a compared snapshot and the object that appears to be leaking.
I have two event listeners bound to OnLogReceived; they look like this:
ApplicationContext.Current.LoggerContext.OnLogReceived += LoggerContext_OnLogReceived;
ApplicationContext.Current.LoggerContext.OnLogReceived += (logLevel, timestamp, message) => RemoteLogHelper.SendMessage(logLevel, message);
Here is the definition of first handler. This displays the log in a scrolling viewer on the screen and truncates the entries to 1000. The LogEntryModel implements INotifyPropertyChanged via ViewModelBase to avoid leaking PropertyChanged.
private async void LoggerContext_OnLogReceived(LogLevel logLevel, DateTime timestamp, string message)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
var entry = new LogEntryModel
{
Message = string.Format("[{0:h:mm:ss tt}] {1}", timestamp, message)
};
entry.SetDispatcher(ApplicationContext.Current.Dispatcher);
lock (ApplicationContext.Current.ReceiverDetails.Log)
{
ApplicationContext.Current.ReceiverDetails.Log.Add(entry);
while (ApplicationContext.Current.ReceiverDetails.Log.Count > 1000)
{
ApplicationContext.Current.ReceiverDetails.Log.RemoveAt(0);
}
}
if (!uxPreventLogFromScrolling.IsChecked ?? false)
{
LogScrollViewer.ChangeView(0.0d, double.MaxValue, 1.0f);
}
});
}
And the send message handler (2) This sends the log lines to a remote API (if remote logging is enabled for this device) for debugging purposes. We want this to be fire & forget, meaning we don't want to wait around for the log to be successfully sent to the endpoint.
public static async void SendMessage(LogLevel logLevel, string message)
{
if (!IsEnabled())
return;
await Task.Run(() =>
{
try
{
ApplicationContext.Current.ApiClient.PostAsync<LogRequestModel, object>("/api/remote/receiver/log", new LogRequestModel { MacAddress = MacAddress, Text = message });
}
catch (Exception)
{
; // we tried
}
});
}
Changing this method to the following definition does not change the behavour (still showing a similar snapshot result)
public static async void SendMessage(LogLevel logLevel, string message)
{
if (!IsEnabled())
return;
try
{
await ApplicationContext.Current.ApiClient.PostAsync<LogRequestModel, object>("/api/remote/receiver/log", new LogRequestModel { MacAddress = MacAddress, Text = message });
}
catch (Exception)
{
; // we tried
}
}
Can anyone pinpoint why this is leaking and how I avoid it? From research, I don't think I need to dispose the tasks and everything here looks like it should be getting cleaned up automatically on completion. However I can definitly see the memory usage growing and the snapshots confirm this.