Ручки растут при использовании ниток - почему? - PullRequest
1 голос
/ 08 августа 2011

У меня есть этот пример кода, и я пытаюсь выяснить, что происходит.

    private static AutoResetEvent autoEvent = new AutoResetEvent(false);
    private static Thread t1;
    static void DoSomething()
    {
        Console.WriteLine("Main starting.");

        ThreadPool.QueueUserWorkItem(
            WorkMethod, autoEvent);

        // Wait for work method to signal.
        autoEvent.WaitOne();

        // trying out does resource cleanup by using dispose and null where possible
        autoEvent.SafeWaitHandle.Dispose();
        t1 = null;
        autoEvent = null;
        autoEvent = new AutoResetEvent(false);
        Console.WriteLine("Work method signaled.\nMain ending.");
    }

    static Action messageTarget; 

    static void WorkMethod(object stateInfo)
    {
        Console.WriteLine("Work starting.");
          // This line is going to change
          messageTarget =  delegate()
                 {
                     Thread.Sleep(new Random().Next(100, 2000));
                 };

        // Signal that work is finished.
        Console.WriteLine("Work ending.");
        ((AutoResetEvent)stateInfo).Set();
    }

Это прекрасно работает и создает 7 дескрипторов после цикла из 100 циклов (счетчик дескрипторов с использованием снимка памяти TestApi).

Теперь интересное поведение таково: когда я оборачиваюделегат в потоке

        t1 = new Thread
            (
          delegate()
                 {
                     Thread.Sleep(new Random().Next(100, 2000));
                 });
        t1.Start();

Приложение заканчивается примерно с 295 дескрипторами!

Я слышал, что .net framework плохо работает с потоками и очищает ресурсы, это правильно?Возможно, некоторые потоки все еще работают в фоновом режиме, когда приложение завершает, но уверены, что это немного экстремальное поведение?

Мой вопрос заключается в том, что вызывает такое большое количество ручек?(Обратите внимание, что это моделирует некоторое поведение в другом приложении и предназначено не для производства, а скорее для понимания, почему количество дескрипторов так резко увеличивается при использовании потоков)

Решение с Thread.Join

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;
using Microsoft.Test.LeakDetection;

namespace FaultDetection
{
     public partial class Form1 : Form
     {
      private Process process;

    public Form1()
    {
        InitializeComponent();

        foreach (Process clsProcess in Process.GetProcesses())
        {

            if (clsProcess.ProcessName.Contains("FaultDetection"))
            {
                //if the process is found to be running then we
                //return a true
                process = clsProcess;
            }
        }

        MemorySnapshot s1;
        if (process != null)
        {
            s1 = MemorySnapshot.FromProcess(process.Id);

            for (int i = 0; i < 100; i++)
            {
                DoSomething();
                MemorySnapshot s2 = MemorySnapshot.FromProcess(process.Id);

                // Compare the two memory snapshots and generate a diff.
                // Then display the diff to the console.
                MemorySnapshot diff = s2.CompareTo(s1);

                Console.WriteLine("\tHandle Count: {0}", diff.HandleCount);
                label1.Text = "Handle Count: "+ diff.HandleCount + "\n";
            }
        }
    }

    private static AutoResetEvent autoEvent = new AutoResetEvent(false);
    private static Thread t1;
    private static List<Thread> threadReferences;

    static void DoSomething()
    {
        Console.WriteLine("Main starting.");

        ThreadPool.QueueUserWorkItem(
            WorkMethod, autoEvent);

        // Wait for work method to signal.
        autoEvent.WaitOne();

        t1.Join();
        autoEvent.SafeWaitHandle.Dispose();
        t1 = null;
        autoEvent = null;
        autoEvent = new AutoResetEvent(false);
        Console.WriteLine("Work method signaled.\nMain ending.");
    }

    static Action messageTarget; 

    static void WorkMethod(object stateInfo)
    {
        Console.WriteLine("Work starting.");
        t1 = new Thread
            (
          delegate()
                 {
                     Thread.Sleep(new Random().Next(100, 2000));
                 });
        t1.Start();
        //messageTarget = delegate() { Thread.Sleep(new Random().Next(100, 2000)); };

        // Signal that work is finished.
        Console.WriteLine("Work ending.");
        ((AutoResetEvent)stateInfo).Set();
    }
}

}

Ответы [ 2 ]

2 голосов
/ 08 августа 2011

Во-первых, .Net Framework не плох в потоке и очистке ресурсов. Не уверен, где вы слышали это, ссылка на ресурс будет хорошей.

Возникли проблемы с вашим кодом:

  1. Если вы звоните DoSomething несколько раз, почему вы выбрасываете и создаете новый AutoResetEvent? Вы можете повторно использовать экземпляр и очистить в конце цикла.

  2. messageTarget не используется в версии, которая не использует Thread напрямую.

  3. В версии, которая использует Thread напрямую, вы создаете и запускаете новый Thread в каждом цикле - почему? Вы получите кучу запущенных потоков. Поскольку ничто не ждет их завершения, все они, скорее всего, будут работать в конце вашего цикла. В конце концов их периоды сна закончатся, и они выйдут, но ваш цикл завершится раньше, чем закончится первый поток.

Я подозреваю, что дескрипторы связаны с запущенными вами потоками. Вам нужно дождаться окончания ваших тем, например, используя Thread.Join. Ваш пример на самом деле не демонстрирует ничего полезного с потоками, он просто создает их много.

Обновление

В ответ на обновление вашего вопроса, как правило, вместо непосредственного создания потоков вы должны использовать ThreadPool или TPL (который использует пул потоков под капотом). Пулы потоков существуют по той причине, что они эффективно управляют ресурсами потоков.

Если вы создаете Thread напрямую, могут быть выделены некоторые дескрипторы (я считаю, что CLR может повторно использовать некоторые потоки, поэтому картинка немного усложняется).

0 голосов
/ 08 августа 2011

Если вы используете пул потоков, как в исходном коде, пул потоков автоматически ограничит количество потоков и событий, которые вы собираетесь создать.Если вы создаете потоки явно, BCL будет делать то, что запрашивается: он будет создавать все потоки.Каждый поток создаст дескриптор, который будет закрыт, когда вы больше не используете объект потока (т. Е. Когда он завершит работу. Я предполагаю, что вам нужно присоединиться к потоку, чтобы сообщить CLR, что вы этого не делаетенужна ручка, но это только предположение).

...