В .NET есть планировщик потоков для длинных потоков?

Наш сценарий - сетевой сканер.

Он подключается к набору хостов и периодически сканирует их параллельно с использованием фоновых потоков с низким приоритетом.

Я хочу, чтобы иметь возможность планировать много работы, но только если у вас есть десять или сколько-нибудь количество хостов, проверенных параллельно. Даже если я создаю свои собственные потоки, многие обратные вызовы и другая асинхронная доброта используют ThreadPool, и у меня заканчивается нехватка ресурсов. Я должен посмотреть на MonoTorrent...

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

Есть ли пул потоков, который я могу инициализировать для n долговечных потоков?

[изменить] Никто, кажется, не заметил, что я сделал несколько комментариев по некоторым ответам, поэтому я добавлю пару вещей здесь.

  • Темы должны быть отменены как изящно и решительно.
  • Темы должны иметь низкий приоритет, оставляя отзывчивый интерфейс.
  • Длины продолжаются, но в порядке (минуты), а не в порядке (дни).

Работа для данного целевого хоста в основном:

  For each test
    Probe target (work is done mostly on the target end of an SSH connection)
    Compare probe result to expected result (work is done on engine machine)
  Prepare results for host

Может кто-нибудь объяснить, почему использование SmartThreadPool отмечено отрицательной полезностью wit ha?

Ответ 1

В .NET 4 у вас есть интегрированная Параллельная библиотека задач. Когда вы создаете новую задачу (новая абстракция потока), вы можете указать задачу долгое время. Мы хорошо поработали с этим (длительные дни, а не минуты или часы).

Вы также можете использовать его в .NET 2, но там есть расширение, проверьте здесь.

В VS2010 Отладка параллельных приложений на основе задач (не потоков) была радикально улучшена. Он рекомендовал использовать Задачи, когда это возможно, а не сырые потоки. Так как он позволяет обрабатывать parallelism более дружественным объектно-ориентированным способом.

UPDATE
Задачи, которые НЕ указываются как длинные, помещаются в очередь в пул потоков (или любой другой планировщик, если на то пошло).
Но если задача определена как долго работающая, она просто создает автономный поток, пул потоков не задействован.

Ответ 2

CLR ThreadPool не подходит для выполнения долговременных задач: для выполнения коротких задач, где затраты на создание поток будет почти таким же высоким, как выполнение самого метода. (Или, по крайней мере, значительный процент времени, необходимого для выполнения метода.) Как вы видели, сама .NET расходует потоки пула потоков, вы не можете зарезервировать их для себя, чтобы вы не рискуете голодать во время выполнения.

Планирование, дросселирование и отмена работы - это другое дело. Нет другого встроенного пула потоков .NET рабочей очереди .NET, поэтому у вас есть своя собственная (управление threads или BackgroundWorkers самостоятельно) или найти существующий (Ami Bar SmartThreadPool выглядит многообещающий, хотя я сам не использовал его).

Ответ 3

В вашем конкретном случае лучшим вариантом будет не ни поток, ни пул потоков, ни рабочий фон, а модель программирования асинхронного программирования (BeginXXX, EndXXX), предоставляемая каркасом.

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

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

Пример асинхронного клиентского сокета - MSDN

P.S. Для нескольких параллельных и длинных заданий, которые не выполняют вычислений, но в основном ждут ввода-вывода (сеть, диск и т.д.), Лучшим вариантом всегда является использование механизма обратного вызова, а не потоков.

Ответ 4

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

Вы можете изменить максимальную текущую границу потока из своего пользовательского интерфейса, и вы можете передать дополнительную информацию в обратный вызов ThreadDone для мониторинга производительности и т.д. Если поток не работает, скажем, тайм-аут сети, вы можете снова вставить обратно в очередь. Добавьте дополнительные методы управления в Supervisor для приостановки, остановки и т.д.

using System;
using System.Collections.Generic;
using System.Threading;

namespace ConsoleApplication1
{
    public delegate void CallbackDelegate(int idArg);

    class Program
    {
        static void Main(string[] args)
        {
            new Supervisor().Run();
            Console.WriteLine("Done");
            Console.ReadKey();
        }
    }

    class Supervisor
    {
        Queue<System.Threading.Thread> waitingThreads = new Queue<System.Threading.Thread>();
        Dictionary<int, System.Threading.Thread> activeThreads = new Dictionary<int, System.Threading.Thread>();
        int maxRunningThreads = 10;
        object locker = new object();
        volatile bool done;

        public void Run()
        {
            // queue up some threads
            for (int i = 0; i < 50; i++)
            {
                Thread newThread = new Thread(new Worker(ThreadDone).DoWork);
                newThread.IsBackground = true;
                waitingThreads.Enqueue(newThread);
            }
            LaunchWaitingThreads();
            while (!done) Thread.Sleep(200);
        }

        // keep starting waiting threads until we max out
        void LaunchWaitingThreads()
        {
            lock (locker)
            {
                while ((activeThreads.Count < maxRunningThreads) && (waitingThreads.Count > 0))
                {
                    Thread nextThread = waitingThreads.Dequeue();
                    activeThreads.Add(nextThread.ManagedThreadId, nextThread);
                    nextThread.Start();
                    Console.WriteLine("Thread " + nextThread.ManagedThreadId.ToString() + " launched");
                }
                done = (activeThreads.Count == 0) && (waitingThreads.Count == 0);
            }
        }

        // this is called by each thread when it done
        void ThreadDone(int threadIdArg)
        {
            lock (locker)
            {
                // remove thread from active pool
                activeThreads.Remove(threadIdArg);
            }
            Console.WriteLine("Thread " + threadIdArg.ToString() + " finished");
            LaunchWaitingThreads(); // this could instead be put in the wait loop at the end of Run()
        }
    }

    class Worker
    {
        CallbackDelegate callback;
        public Worker(CallbackDelegate callbackArg)
        {
            callback = callbackArg;
        }

        public void DoWork()
        {
            System.Threading.Thread.Sleep(new Random().Next(100, 1000));
            callback(System.Threading.Thread.CurrentThread.ManagedThreadId);
        }
    }
}