Как вы получаете список запущенных потоков в C #? - PullRequest
6 голосов
/ 22 февраля 2012

Я создаю динамические потоки в C #, и мне нужно получить статус этих запущенных потоков.

List<string>[] list;
list = dbConnect.Select();

for (int i = 0; i < list[0].Count; i++)
{
    Thread th = new Thread(() =>{
        sendMessage(list[0]['1']);
        //calling callback function
    });
    th.Name = "SID"+i;
    th.Start();
}

for (int i = 0; i < list[0].Count; i++)
{
    // here how can i get list of running thread here.
}

Как получить список запущенных потоков?

Ответы [ 5 ]

37 голосов
/ 23 февраля 2012

В потоках

Я бы не стал явно создавать потоки самостоятельно.

Гораздо предпочтительнее использовать ThreadPool.QueueUserWorkItem или, если вы действительно можете использовать .Net 4.0, вы получите гораздо более мощную параллельную библиотеку задач , которая также позволяетиспользовать потоки ThreadPool гораздо более мощным способом (Task.Factory.StartNew стоит посмотреть)

Что, если мы решим использовать явное создание потоков?

Предположим, что ваш список [0] .Count возвращает 1000 элементов.Предположим также, что вы выполняете это на высокопроизводительной (на момент написания этой статьи) 16-ядерной машине.Непосредственный эффект состоит в том, что у нас есть 1000 потоков , конкурирующих за эти ограниченные ресурсы (16 ядер).

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

Таким образом, в то время как первоначальное намерение многопоточности может быть увеличить скорость, как мы можем видеть, он может иметь противоположный эффект.

Как мы преодолеваем «чрезмерную» многопоточность?

Здесь вступает в игру ThreadPool.

Пул потоков - это набор потоков, которые можно использовать для выполнения ряда задач в фоновом режиме.

Как они работают:

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

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

Таким образом, мы можем видеть, что при использовании потоков пула потоков мы более эффективны как

  • с точки зрения максимизации фактической работы Выполнение.Поскольку мы не слишком насыщаем процессоры потоками, меньше времени тратится на переключение между потоками и больше времени на выполнение кода, который должен делать поток.
  • Ускоренный запуск потока : Каждый поток пула доступен в отличие от ожидания создания нового потока.
  • с точки зрения минимизации потребления памяти , пул потоков будет ограничивать количество потоков размером пула потоков, ставя в очередь любые запросы, превышающие ограничение размера пула потоков.(см. ThreadPool.GetMaxThreads).Основная причина такого выбора дизайна, разумеется, заключается в том, что мы не перенасыщаем ограниченное количество ядер слишком большим количеством запросов потоков, сохраняя переключение контекста на более низкие уровни.

Слишком много теории, давайте проверим всю эту теорию!

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

новый поток

    static void Main(string[] args)
    {
        int itemCount = 1000;

        Stopwatch stopwatch = new Stopwatch(); 
        long initialMemoryFootPrint = GC.GetTotalMemory(true);

        stopwatch.Start();
        for (int i = 0; i < itemCount; i++)
        {
            int iCopy = i;  // You should not use 'i' directly in the thread start as it creates a closure over a changing value which is not thread safe. You should create a copy that will be used for that specific variable.
            Thread thread = new Thread(() =>
            {
                // lets simulate something that takes a while
                int k = 0;
                while (true)
                {
                    if (k++ > 100000)
                        break;
                }

                if ((iCopy + 1) % 200 == 0) // By the way, what does your sendMessage(list[0]['1']); mean? what is this '1'? if it is i you are not thread safe.
                    Console.WriteLine(iCopy + " - Time elapsed: (ms)" + stopwatch.ElapsedMilliseconds);
            });

            thread.Name = "SID" + iCopy; // you can also use i here. 
            thread.Start();
        }

        Console.ReadKey();
        Console.WriteLine(GC.GetTotalMemory(false) - initialMemoryFootPrint);
        Console.ReadKey();
    }

Результат:

New Thread Benchmark

ThreadPool.EnqueueUserWorkItem

    static void Main(string[] args)
    {
        int itemCount = 1000;

        Stopwatch stopwatch = new Stopwatch(); 
        long initialMemoryFootPrint = GC.GetTotalMemory(true);

        stopwatch.Start();

        for (int i = 0; i < itemCount; i++)
        {
            int iCopy = i; // You should not use 'i' directly in the thread start as it creates a closure over a changing value which is not thread safe. You should create a copy that will be used for that specific variable.
            ThreadPool.QueueUserWorkItem((w) =>
            {
                // lets simulate something that takes a while
                int k = 0;
                while (true)
                {
                    if (k++ > 100000)
                        break;
                }

                if ((iCopy + 1) % 200 == 0) 
                    Console.WriteLine(iCopy + " - Time elapsed: (ms)" + stopwatch.ElapsedMilliseconds);
            });
        }

        Console.ReadKey();
        Console.WriteLine("Memory usage: " + (GC.GetTotalMemory(false) - initialMemoryFootPrint));
        Console.ReadKey();
    }

Результат:

ThreadPool Benchmark

Параллельная библиотека задач (TPL)

    static void Main(string[] args)
    {
        int itemCount = 1000;

        Stopwatch stopwatch = new Stopwatch(); 
        long initialMemoryFootPrint = GC.GetTotalMemory(true);

        stopwatch.Start();
        for (int i = 0; i < itemCount; i++)
        {
            int iCopy = i;  // You should not use 'i' directly in the thread start as it creates a closure over a changing value which is not thread safe. You should create a copy that will be used for that specific variable.
            Task.Factory.StartNew(() =>
            {
                // lets simulate something that takes a while
                int k = 0;
                while (true)
                {
                    if (k++ > 100000)
                        break;
                }

                if ((iCopy + 1) % 200 == 0) // By the way, what does your sendMessage(list[0]['1']); mean? what is this '1'? if it is i you are not thread safe.
                    Console.WriteLine(iCopy + " - Time elapsed: (ms)" + stopwatch.ElapsedMilliseconds);
            });
        }

        Console.ReadKey();
        Console.WriteLine("Memory usage: " + (GC.GetTotalMemory(false) - initialMemoryFootPrint));
        Console.ReadKey();
    }

Результат:

Task Parallel Library result

Итак, мы можем видеть, что:

+--------+------------+------------+--------+
|        | new Thread | ThreadPool |  TPL   |
+--------+------------+------------+--------+
| Time   | 6749       | 228ms      | 222ms  |
| Memory | ≈300kb     | ≈103kb     | ≈123kb |
+--------+------------+------------+--------+

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

На данный момент мы доказали, что использование потоков ThreadPool является предпочтительным вариантом с точки зрения скорости и памяти.

Тем не менее, мы не ответили на ваш вопрос.Как отследить состояние запущенных потоков.

Чтобы ответить на ваш вопрос

Учитывая полученные нами идеи, я подхожу к этому так:

        List<string>[] list = listdbConnect.Select()
        int itemCount = list[0].Count;
        Task[] tasks = new Task[itemCount];
        stopwatch.Start();
        for (int i = 0; i < itemCount; i++)
        {
            tasks[i] = Task.Factory.StartNew(() =>
            {
                // NOTE: Do not use i in here as it is not thread safe to do so! 
                sendMessage(list[0]['1']);
                //calling callback function
            });
        }

        // if required you can wait for all tasks to complete
        Task.WaitAll(tasks);

        // or for any task you can check its state with properties such as: 
        tasks[1].IsCanceled
        tasks[1].IsCompleted
        tasks[1].IsFaulted 
        tasks[1].Status

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

Удачи!

11 голосов
/ 22 февраля 2012

Использование Process.Threads:

var currentProcess = Process.GetCurrentProcess();
var threads = currentProcess.Threads;

Примечание: любые потоки, принадлежащие текущему процессу, будут отображаться здесь, включая те, которые не были созданы вами явно.

Если вам нужны только созданные вами потоки, ну почему бы вам не отслеживать их при создании?

8 голосов
/ 22 февраля 2012

Создайте List<Thread> и сохраняйте каждый новый поток в вашем первом цикле for.

List<string>[] list;
List<Thread> threads = new List<Thread>();
list = dbConnect.Select();

for (int i = 0; i < list[0].Count; i++)
{
    Thread th = new Thread(() =>{
        sendMessage(list[0]['1']);
        //calling callback function
    });
    th.Name = "SID"+i;
    th.Start();
    threads.add(th)
}

for (int i = 0; i < list[0].Count; i++)
{
    threads[i].DoStuff()
}

Однако, если вам не нужно i, вы можете сделать второй цикл foreach вместо for


В качестве примечания: если ваша sendMessage функция выполняется не очень долго, вам нужно немного меньше веса, чем полной нити, используйте ThreadPool.QueueUserWorkItem или, если она вам доступна, a Задание

2 голосов
/ 22 февраля 2012
Process.GetCurrentProcess().Threads

Это дает вам список всех потоков, запущенных в текущем процессе, но имейте в виду, что есть потоки, отличные от тех, которые вы запустили сами.

0 голосов
/ 22 февраля 2012

Используйте Process.Threads для просмотра ваших тем.

...