Почему это так долго для GC System.Threading.OverlappedData?

Я запускаю свое приложение через профилировщик памяти для проверки утечек. Кажется, что все в порядке, но я получаю много этих OverlappedData, которые, кажется, висят вокруг в очереди финализатора, практически ничего не делая. Они являются результатом перекрытия IO, который был отменен путем отключения базового NetworkStream на обоих концах соединения.

Сам сетевой поток. В настоящее время нет живых экземпляров NetworkStream.

Обычно они внедряются во что-то, что называется OverlappedDataCacheLine. Я вызываю EndRead в обратном вызове, первое, что я делаю, поэтому вызов BeginRead не должен иметь соответствующий EndRead.

Это довольно типичный вид, который удерживает его от инструмента

OverlappedData is being help up

В конце концов, он получает GC'd, но он берет навсегда - в порядке получаса, чтобы убить все, когда я начал около тысячи потоков, поместил их в асинхронный вызов BeginRead и закрыл их вниз примерно через минуту.

Эта программа несколько раз воспроизводит проблему с веб-сервером на порту 80. Любой веб-сервер будет делать действительно.

using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Threading;

class Program
{
    static void Main(string[] args)
    {
        var clients = new List<TcpClient>();
        for (int i = 0; i < 1000; ++i) {
            var client = new TcpClient();
            clients.Add(client);

            client.BeginConnect("localhost", 80, connectResult =>
                {
                    client.EndConnect(connectResult);

                    var stream = client.GetStream();
                    stream.BeginRead(new byte[1000], 0, 1000, result =>
                        {
                            try
                            {
                                stream.EndRead(result);
                                Console.WriteLine("Finished (should not happen)");
                            }
                            catch
                            {
                                // Expect to find an IO exception here
                                Console.WriteLine("Faulted");                               
                            }
                        }, stream);     
                }, client);             
        }

        Thread.Sleep(10000); // Make sure everything has time to connect

        foreach (var tcpClient in clients)
        {
            tcpClient.GetStream().Close();
            tcpClient.Close();
        }
        clients.Clear(); // Make sure the entire list can be GC'd

        Thread.Sleep(Timeout.Infinite); // Wait forever. See in profiler to see the overlapped IO take forever to go away
    }
}

Конечно, эта программа не берет навсегда, чтобы очистить тысячу OverlappedData, так как она меньше, чем реальное приложение, но для выполнения этой задачи требуется некоторое время. Я получаю предупреждения за застрявший финализатор при запуске моих реальных вещей вместо этого тестового приложения. Это мало что делает в моем приложении, просто пытается закрыть все, что не могло быть закрыто, и гарантирует, что ссылки нигде не будут сохранены.

Это вообще не имеет значения, если я назову Dispose() или Close() на клиенте и потоке. Результат тот же.

Любая подсказка о том, почему это происходит и как этого избежать? Это CLR умнее на мне и, возможно, сохранить эти блокированные блоки памяти в состоянии подготовки к новым вызовам? И почему финализатор настолько невероятно медленно завершает работу?

Обновить После выполнения невероятно тупых нагрузочных тестов, поставив стакан воды на ключ F5 и получив кофе, кажется, что что-то вызывает более полный GC при стрессе, который собирает эти вещи. Таким образом, на самом деле нет реальной проблемы, но все же было бы неплохо узнать, что на самом деле происходит здесь, и почему сбор этого объекта является величиной медленнее, чем другие объекты, и если это потенциально может быть проблемой на более позднем этапе с фрагментированным памяти и т.д.

Ответы

Ответ 1

Хорошо, теперь ясно, что происходит. Сбор мусора всегда случается, когда вы выделяете память. Для этого требуется не менее 2 мегабайт распределений, типичный начальный размер кучи генерации 0 GC для запуска GC. Другими словами, программа, которая ничего не делает, никогда не запускает GC, и вы увидите какие-либо объекты, которые еще не были собраны в куче с профилировщиком памяти в течение длительного времени.

Это хорошее объяснение того, что вы описываете. После завершения всех подключений ваша программа больше ничего не должна делать. Поэтому не будет выделяться много, если какая-либо память не вызовет сборку. Если ваш профилировщик не показывает коллекции, вы можете увидеть их с помощью Perfmon.exe. В противном случае это не проблема, просто побочный эффект работы сборщика мусора.

Всегда беспокоиться о утечках, когда у вас есть четкие доказательства того, что у программы есть проблема с потреблением ресурсов.

Ответ 2

Попробуйте получить клиент от AsyncResult, чтобы исключить любые проблемы с закрытием лямбда.

TcpClient t = (TcpClient)connectResult.AsyncState;

Кроме того, не следует ли вам позвонить EndConnect после завершения обработки?