Простейшая реализация шаблона await
public class MyAwaitableType
{
public MinimalAwaiter GetAwaiter()
{
return new MinimalAwaiter();
}
public class MinimalAwaiter : INotifyCompletion
{
public bool IsCompleted => true;
public string GetResult() => "This is a result";
public void OnCompleted(Action continuation)
{
throw new NotImplementedException();
}
}
}
1 Ұнайды
Наличие не обязательно означает достижимость. Объект мог стать недоступным со времени последней сборки мусора. Или, возможно, с момента выделения объекта вообще не было сборки мусора. TryGetTarget заботит не то, доступен ли объект прямо сейчас, а лишь то, обнаружил ли сборщик мусора, что он подлежит освобождению.
Если объект доступен, TryGetTarget предоставляет его через параметр out, и это будет строгой ссылкой. Таким образом, если метод возвращает значение true, нам не нужно беспокоиться о том, что через мгновение объект может стать недоступным, — тот факт, что мы теперь сохранили эту ссылку в переменной, которую вызывающая сторона передала через аргумент cachedItem, сохранит и сам объект. Если TryGetTarget возвращает false, мой код удаляет соответствующую запись из словаря, поскольку она представляет объект, которого больше не существует. Это важно, потому что даже если слабая ссылка не сохранит свою цель, WeakReference<T> — это отдельный объект, и сборщик не может освободить его, пока я не удалю его из этого словаря.
Этот кэш хранит все значения с помощью WeakReference<T>. Его метод Add передает объект, для которого мы хотели бы использовать слабую ссылку в качестве аргумента конструктора для нового объекта WeakReference<T>. Метод TryGetValue пытается получить значение, ранее сохраненное с помощью Add. Сначала проверяется, содержит ли словарь соответствующую запись. Если содержит, то значением этой записи будет WeakReference<T>, которую мы создали ранее. Мой код вызывает метод TryGetTarget слабой ссылки, который вернет true, если объект все еще доступен, и false, если нет.
Листинг 7.3. Использование слабых ссылок в кэше
public class WeakCache<TKey, TValue> where TValue : class
{
private readonly Dictionary<TKey, WeakReference<TValue>> _cache =
new Dictionary<TKey, WeakReference<TValue>>();
public void Add(TKey key, TValue value)
{
_cache.Add(key, new WeakReference<TValue>(value));
}
public bool TryGetValue(TKey key, out TValue cachedItem)
{
WeakReference<TValue> entry;
if (_cache.TryGetValue(key, out entry))
{
bool isAlive = entry.TryGetTarget(out cachedItem);
if (!isAlive)
{
_cache.Remove(key);
}
return isAlive;
}
else
{
cachedItem = null;
return false;
}
}
}
Хотя сборщик мусора будет проходить по обычным ссылкам в полях достижимого объекта, можно сохранить слабую ссылку. Сборщик не проходит по слабым ссылкам, поэтому, если единственный способ достичь объекта — это слабые ссылки, сборщик ведет себя так, как будто объект недоступен, и удаляет его. Слабая ссылка — это способ сообщить CLR: «Не ориентируйся на меня при сохранении этого объекта, но пока он еще кому-то нужен, я бы хотел иметь к нему доступ»
В более общем смысле, если ваша программа делает объект достижимым, сборщик мусора не может определить, собираетесь ли вы снова использовать этот объект, поэтому он должен быть сохранен.
Таким образом, если вы добавляете объекты в коллекции и сохраняете доступность этих коллекций, сборщик мусора будет считать все в этих коллекциях достижимым. Удаление элементов — это ваша задача.
Листинг 7.2. Крайне неэффективный кусок кода
static void Main(string[] args)
{
var numbers = new List<string>();
long total = 0;
for (int i = 1; i < 100_000; ++i)
{
numbers.Add(i.ToString());
total += i;
}
Console.WriteLine("Total: {0}, average: {1}",
total, total / numbers.Count);
}
Одним из важных результатов определения достижимости является то, что сборщик мусора не смущают циклические ссылки. Это одна из причин, по которой .NET использует сборку мусора вместо подсчета ссылок (еще один популярный подход для автоматизации управления памятью). Если у вас есть два объекта, которые ссылаются друг на друга, схема подсчета ссылок решит, что оба объекта используются, поскольку каждый из них упоминается как минимум один раз. Но объекты могут быть недоступны — если нет других ссылок на них, приложение не может их использовать. Подсчет ссылок не способен этого обнаружить, поэтому он может стать причиной утечек памяти. Но для схемы, использующей сборщик мусора CLR, тот факт, что они ссылаются друг на друга, не имеет значения — сборщик мусора никогда не доберется ни до одного из них, поэтому он правильно определит, что они больше не используются.
Составив полный список текущих корневых ссылок для всех потоков, сборщик мусора определяет, какие объекты могут быть доступны по этим ссылкам. Он просматривает каждую ссылку по очереди, и, если она не равна null, сборщик знает, что объект, на который она ссылается, достижим. Среди них могут содержаться дубликаты, так как несколько корневых ссылок могут ссылаться на один и тот же объект. В связи с этим сборщик мусора отслеживает, какие объекты он уже встречал. Для каждого нового обнаруженного объекта сборщик добавляет все поля экземпляра ссылочного типа из этого объекта в список ссылок для оценки, опять же, избавляясь от дубликатов. (Это включает в себя скрытые поля, сгенерированные компилятором, такие как поля для автоматических свойств, которые я описал в главе 3.) Это означает, что если объект достижим, это относится и ко всем объектам, на которые он ссылается. Сборщик мусора повторяет этот процесс, пока у него не закончатся ссылки для проверки. Любые объекты, которые он не обозначил доступными, считаются недоступными, потому что сборщик просто делает то, что делает программа: использует только те объекты, которые доступны прямо или косвенно через ее переменные, временное локальное хранилище, статические поля и другие корневые ссылки.
