Как гарантировать создание нового потока при использовании метода Task.StartNew

#c# #.net #multithreading #.net-4.0 #task-parallel-library

#c# #.net #многопоточность #.net-4.0 #задача-параллельная библиотека

Вопрос:

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

На данный момент я делаю это так:

 var task = Task.Factory.StartNew
         (CheckFiles
          , cancelCheckFile.Token
          , TaskCreationOptions.LongRunning
          , TaskScheduler.Default);//Check for files on another thread

 private void CheckFiles()
 {
    while (!cancelCheckFile.Token.IsCancellationRequested)
    {
        //do stuff
    }
 }
  

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

В прошлом я делал что-то подобное:

 thQueueChecker = new Thread(new ThreadStart(CheckQueue));
thQueueChecker.IsBackground = true;
thQueueChecker.Name = "CheckQueues"   DateTime.Now.Ticks.ToString();
thQueueChecker.Start();


private void CheckQueue()
{
   while (!ProgramEnding)
   {
            //do stuff
   }
}
  

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

Комментарии:

1. LongRunning это просто подсказка для планировщика — если вам абсолютно необходимо всегда иметь новый поток, вам придется его создать.

2. Хотите добавить это в качестве ответа?

3. Я думаю, что общая проблема, которая не касается ответов, заключается в том, что не гарантируется создание нового потока, но он всегда будет использовать поток из ThreadPool, он может просто повторно использовать поток, который простаивает в пуле, не выполняя никакой работы. Если вы вызываете Task.Factory.StartNew из потока, отличного от ThreadPool, он всегда будет запускать код в другом потоке, вы просто не знаете, является ли этот поток совершенно новым или использовался ранее для запуска другого кода.

Ответ №1:

Планировщик задач по умолчанию ThreadPoolTaskScheduler действительно всегда создает новый поток для длительно выполняющейся задачи. Как вы можете видеть, он не использует пул потоков. Это ничем не отличается от ручного подхода к созданию потока самостоятельно. Теоретически может случиться так, что планировщик потоков .NET 4.5 делает что-то другое, но на практике это вряд ли изменится.

 protected internal override void QueueTask(Task task)
{     
  if ((task.Options amp; TaskCreationOptions.LongRunning) != TaskCreationOptions.None)
  {
    new Thread(s_longRunningThreadWork) { IsBackground = true }.Start(task);
  }
  else
  {
    bool forceGlobal = 
        (task.Options amp; TaskCreationOptions.PreferFairness) != TaskCreationOptions.None;
     ThreadPool.UnsafeQueueCustomWorkItem(task, forceGlobal);
  }
}
  

Комментарии:

1. Я не вижу, что не так с моим ответом. Не могли бы вы уточнить, каковы были ваши рассуждения о понижении?

2. @Ramhound Я понимаю, что это древний, но xp ! = правильный ответ. Alois верен, смотрите Исходный код .NET Framework для ThreadPoolTaskScheduler второго метода вниз QueueTask : referencesource.microsoft.com/#mscorlib/system/threading/Tasks/ThreadPoolTaskScheduler.cs В коде даже есть комментарий: // Запускать задачи длительного выполнения в их собственном выделенном потоке.

Ответ №2:

Это зависит от используемого вами планировщика. Существует две стандартные реализации, ThreadPoolTaskScheduler и SynchronizationContextTaskScheduler. Последний вообще не запускает поток, используемый методом FromCurrentSynchronizationContext().

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

Комментарии:

1. Почему я не могу найти какой-либо результат (онлайн-статья MSDN), выполнив поиск по ThreadPoolTaskScheduler site:msdn.microsoft.com -site:social.msdn.microsoft.com или по SynchronizationContextTaskScheduler site:msdn.microsoft.com -site:social.msdn.microsoft.com ?

2. Потому что они являются внутренними классами. Как я уже упоминал, «детали реализации».

3. Спасибо. Могу ли я спросить, что такое «стандартные реализации»?

4. Стандартные реализации — это классы, производные от TaskScheduler, которые предоставляются вам фреймворком. В отличие от пользовательской реализации, планировщик, который вы создаете самостоятельно, производя из класса TaskScheduler. Что является нетривиальной задачей.

Ответ №3:

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

Комментарии:

1. Я думаю, что это правильный ответ. Мне нравится абстракция потоков с использованием Task, но никто не может гарантировать новый поток. Хенк говорит, что я должен просто позволить ему делать то, что я хочу, но это не гарантирует новый поток.

2. Считаете ли вы комментарии Хенка, предполагающие, что я на неверном пути, обоснованными, и если да, то я хотел бы знать / learn more

3. Я думаю, что путаница связана с такими словами, как «гарантия». LongRunning Опция вообще ничего не «гарантирует». Однако Tasksscheduler по умолчанию в .NET 4 создает новый поток, и это маловероятно , что изменится. Использование задач — это абстракция, которая упрощает код, но вы теряете некоторый контроль. Если это действительно имеет значение для вас, то вы всегда можете написать TaskScheduler, который гарантирует новый поток и имеет лучшее из обоих миров 🙂

4. Интересная идея. У меня нет проблем с созданием потоков и управлением ими, я просто подумал, что теперь основное внимание уделяется тому, чтобы позволить TPL делать это за вас, но если он не может гарантировать новый поток, тогда ваш вариант / ответ лучше. Я также не уверен, что имел в виду Хенк, подразумевая, что существует разница между отдельным потоком и новым потоком. Конечно, это то же самое, если только не существует волшебного скрытого потока, ожидающего, когда я использую команды для его выполнения

5. Как вы уже выяснили, действительно существуют «волшебные» скрытые потоки. Но реальный вопрос в том, почему вам нужны гарантии того, как это будет выполнено? Если у планировщика задач есть основания полагать, что новый поток может не понадобиться, почему вы предполагаете, что знаете лучше? За подобными решениями стоит много теории и компромиссов.

Ответ №4:

Вам нужно будет указать, почему вам «всегда нужен отдельный поток».

 void Main()
{
   var task = Task.Factory.StartNew(CheckFiles, 
     cancelCheckFile.Token, 
     TaskCreationOptions.LongRunning, 
     TaskScheduler.Default);

   task.Wait();
}
  

Умный планировщик будет использовать здесь 1 поток. Почему этого не должно быть?


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

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

Комментарии:

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

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

3. Но как я могу это гарантировать. Откуда вы знаете, что планировщик сделает это за меня в другом потоке? Другая вещь, которую я не указываю, и которую вы делаете, — это task.Wait. Поскольку я хочу это в другом потоке, я не жду ()

4. @Jon — вы все еще не объяснили, почему вам «нужен новый поток». И это было перепутано с «отдельным потоком». В любом случае, вы сейчас на неверном пути.

5. Пример того, когда вам нужно гарантировать другой поток: вы получаете обратный вызов от SetWindowsHookEx , и вам нужно выполнить COM-вызов, например, WMI API. Если вы сделаете это внутри обратного вызова, вы получите исключение: исходящий вызов не может быть выполнен, поскольку приложение отправляет вызов, синхронный с вводом . Вы должны гарантировать , что вы запускаете все, что вам нужно для запуска в отдельном потоке.