РЕДАКТИРОВАТЬ: Хорошо, я начинаю с нуля. Вот короткое, но complete консольное приложение, которое показывает проблему. Он регистрирует время сообщения и поток, в котором он находится:
using System;
using System.Threading;
using System.ComponentModel;
class Test
{
static void Main()
{
for(int i=0; i < 20; i++)
{
Log("Starting " + i);
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += DoWork;
worker.RunWorkerCompleted += Completed;
worker.ProgressChanged += Progress;
worker.RunWorkerAsync(i);
}
Console.ReadLine();
}
static void Log(object o)
{
Console.WriteLine("{0:HH:mm:ss.fff} : {1} : {2}",
DateTime.Now, Thread.CurrentThread.ManagedThreadId, o);
}
private static void Progress(object sender,
ProgressChangedEventArgs args)
{
Log(args.UserState);
}
private static void Completed(object sender,
RunWorkerCompletedEventArgs args)
{
Log(args.Result);
}
private static void DoWork(object sender, DoWorkEventArgs args)
{
BackgroundWorker worker = (BackgroundWorker) sender;
Log("Worker " + args.Argument + " started");
lock (typeof(Test)) // Ensure only a single request at a time
{
worker.ReportProgress(0, "Start");
Thread.Sleep(2000); // Simulate waiting on the request
worker.ReportProgress(50, "Middle");
Thread.Sleep(2000); // Simulate handling the response
worker.ReportProgress(100, "End");
args.Result = args.Argument;
}
}
}
Пример вывода:
14:51:35.323 : 1 : Starting 0
14:51:35.328 : 1 : Starting 1
14:51:35.330 : 1 : Starting 2
14:51:35.330 : 3 : Worker 0 started
14:51:35.334 : 4 : Worker 1 started
14:51:35.332 : 1 : Starting 3
14:51:35.337 : 1 : Starting 4
14:51:35.339 : 1 : Starting 5
14:51:35.340 : 1 : Starting 6
14:51:35.342 : 1 : Starting 7
14:51:35.343 : 1 : Starting 8
14:51:35.345 : 1 : Starting 9
14:51:35.346 : 1 : Starting 10
14:51:35.350 : 1 : Starting 11
14:51:35.351 : 1 : Starting 12
14:51:35.353 : 1 : Starting 13
14:51:35.355 : 1 : Starting 14
14:51:35.356 : 1 : Starting 15
14:51:35.358 : 1 : Starting 16
14:51:35.359 : 1 : Starting 17
14:51:35.361 : 1 : Starting 18
14:51:35.363 : 1 : Starting 19
14:51:36.334 : 5 : Worker 2 started
14:51:36.834 : 6 : Start
14:51:36.835 : 6 : Worker 3 started
14:51:37.334 : 7 : Worker 4 started
14:51:37.834 : 8 : Worker 5 started
14:51:38.334 : 9 : Worker 6 started
14:51:38.836 : 10 : Worker 7 started
14:51:39.334 : 3 : Worker 8 started
14:51:39.335 : 11 : Worker 9 started
14:51:40.335 : 12 : Worker 10 started
14:51:41.335 : 13 : Worker 11 started
14:51:42.335 : 14 : Worker 12 started
14:51:43.334 : 4 : Worker 13 started
14:51:44.335 : 15 : Worker 14 started
14:51:45.336 : 16 : Worker 15 started
14:51:46.335 : 17 : Worker 16 started
14:51:47.334 : 5 : Worker 17 started
14:51:48.335 : 18 : Worker 18 started
14:51:49.335 : 19 : Worker 19 started
14:51:50.335 : 20 : Middle
14:51:50.336 : 20 : End
14:51:50.337 : 20 : Start
14:51:50.339 : 20 : 0
14:51:50.341 : 20 : Middle
14:51:50.343 : 20 : End
14:51:50.344 : 20 : 1
14:51:50.346 : 20 : Start
14:51:50.348 : 20 : Middle
14:51:50.349 : 20 : End
14:51:50.351 : 20 : 2
14:51:50.352 : 20 : Start
14:51:50.354 : 20 : Middle
14:51:51.334 : 6 : End
14:51:51.335 : 6 : Start
14:51:51.334 : 20 : 3
14:51:53.334 : 20 : Middle
(и т.д.)
Теперь пытаемся понять, что происходит ... но важно отметить, что рабочие потоки начинаются с интервалом в 1 секунду.
РЕДАКТИРОВАТЬ: Дальнейшее расследование: если я позвоню ThreadPool.SetMinThreads(500, 500)
, то даже на моем компьютере с Vista, это показывает, что все рабочие начинают в основном вместе.
Что произойдет с вашим устройством, если вы попробуете вышеуказанную программу с вызовом SetMinThreads
и без него? Если это помогает в этом случае, но не вашей реальной программе, вы могли бы создать аналогичную короткую, но полную программу, которая показывает, что она все еще остается проблемой даже при вызове SetMinThreads
?
Я верю, что понимаю это. Я думаю, ReportProgress
добавляет новую задачу ThreadPool
для обработки сообщения ... и в то же время вы заняты добавлением 20 задач в пул потоков. Теперь в пуле потоков заключается в том, что если не хватает потоков, доступных для обслуживания запроса, как только он поступит, пул ждет полсекунды, прежде чем создать новый поток. Это сделано для того, чтобы избежать создания огромного количества потоков для набора запросов, которые можно легко обработать в одном потоке, если вы просто дождетесь завершения существующей задачи.
Итак, в течение 10 секунд вы просто добавляете задачи в длинную очередь и создаете новый поток каждые полсекунды. Все 20 «главных» задач относительно длинные, тогда как задачи ReportProgress
очень короткие - поэтому, как только у вас будет достаточно потоков для обработки всех долго выполняющихся запросов и одного короткого, вы прочь, и все сообщения приходят быстро.
Если добавить вызов к
ThreadPool.SetMaxThreads(50, 50);
до того, как все это начнется, вы увидите, что оно ведет себя так, как вы ожидаете. Я не предлагаю вам обязательно делать это для вашего реального приложения, но просто чтобы показать разницу. Это создаст кучу потоков в пуле для начала, просто ожидая запросов.
Один комментарий к вашему дизайну: у вас есть 20 различных задач в разных потоках, но только одна из них может выполняться одновременно (из-за блокировки). В любом случае вы эффективно сериализуете запросы, так зачем использовать несколько потоков? Я надеюсь, что у вашего реального приложения нет этой проблемы.