#c_sharp #net #многопоточность #логирование
Из разных потоков вызывается метод и надо логировать его работу. Т.к. GC при сборке
мусора в WinForms и WPF приложениях может приостановить работу потоков приложения,
то это надо учитываться при логировании.
Как можно отслеживать запуски GC?
Ответы
Ответ 1
Для того чтобы отслеживать GC надо вызвать метод RegisterForFullGCNotification, а
также WaitForFullGCApproach и WaitForFullGCComplete.
using System.Diagnostics;
using System.Collections.Concurrent;
using System.Threading;
using System.Runtime.CompilerServices;
class LogLine {
public int Number;
public int ThreadId;
public long Ticks;
public object Value;
public long GCMemory;
public int[] GCCollections;
public LogLine(int num, object value, long ticks) {
this.Number = num;
this.Value = value;
this.Ticks = ticks;
this.ThreadId = Environment.CurrentManagedThreadId;
this.GCMemory = GC.GetTotalMemory(false);
int[] arr = new int[GC.MaxGeneration + 1];
for (var i = 0; i < arr.Length; i++) arr[i] = GC.CollectionCount(i);
this.GCCollections = arr;
}
}
class Log : IDisposable {
Stopwatch sw = Stopwatch.StartNew();
BlockingCollection lines = new BlockingCollection();
void Add(LogLine line) {
if (!lines.IsAddingCompleted) lines.Add(line);
}
public Log() {
GC.RegisterForFullGCNotification(1, 1);
new Thread(() => {
while (!lines.IsAddingCompleted) {
Add(new LogLine(-1, GC.WaitForFullGCApproach(), sw.ElapsedTicks));
Add(new LogLine(-2, GC.WaitForFullGCComplete(), sw.ElapsedTicks));
}
}).Start();
}
public void WriteLine(object value = null, [CallerLineNumber] int cnumber = 0) {
Add(new LogLine(cnumber, value, sw.ElapsedTicks));
}
void IDisposable.Dispose() {
GC.CancelFullGCNotification();
Add(new LogLine(-3, "Disposed", sw.ElapsedTicks));
lines.CompleteAdding();
}
public IEnumerable ToCsv() {
var s = ",\t ";
yield return String.Concat(
"Number", s, "Ticks", s, "ThreadId", s, "GCMemory", s,
"GCCollections", s, "Value");
foreach (var l in lines)
yield return String.Concat(
l.Number, s, l.Ticks, s, l.ThreadId, s, l.GCMemory, s,
String.Join(";", l.GCCollections), s, l.Value);
}
}
Для теста в двух потоках создаем и заполняем список массивов по 100 тыс. int
var log = new Log();
using (log) {
Parallel.For(0, 2, i => {
var lst = new List();
log.WriteLine("new List");
try {
while (true) lst.Add(new int[100000]);
}
catch (OutOfMemoryException) {
log.WriteLine("OutOfMemory; lst.Count=" + lst.Count);
}
});
}
Выводим собранные данные
foreach (var line in log.ToCsv())
Console.WriteLine(line);
Результат (получен в C# Interactive, Microsoft (R) Roslyn C# Compiler version 1.1.0.51204)
Number, Ticks, ThreadId, GCMemory, GCCollections, Value
55, 4030, 6, 6905852, 1;0;0, new List
55, 4426, 9, 6905852, 1;0;0, new List
-1, 274370, 11, 1481676908, 6;5;5, Succeeded
-2, 274430, 11, 1482877004, 6;5;5, Succeeded
-1, 277392, 11, 1528080620, 6;5;5, Succeeded
58, 355462, 6, 1528087152, 8;7;7, OutOfMemory; lst.Count=1888
58, 355462, 9, 1528087152, 8;7;7, OutOfMemory; lst.Count=1920
-2, 317257, 11, 1528087152, 8;7;7, Succeeded
-3, 356158, 6, 1528095344, 8;7;7, Disposed