Подтвердить что ты не робот

Как передать флаг LongRunning специально для Task.Run()?

Мне нужен способ установить async-задачу как долгое время без использования Task.Factory.StartNew(...) и вместо этого использовать Task.Run(...) или что-то подобное.

Context:

У меня есть Task, которая непрерывно циклически, пока она не будет отменена извне, которую я хотел бы установить как "длинный" (т.е. дать ему выделенный поток). Это может быть достигнуто с помощью приведенного ниже кода:

var cts = new CancellationTokenSource();
Task t = Task.Factory.StartNew(
async () => {
while (true)
{
    cts.Token.ThrowIfCancellationRequested();
    try
    {
        "Running...".Dump();
        await Task.Delay(500, cts.Token);
    }
    catch (TaskCanceledException ex) { }
} }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

Проблема заключается в том, что Task.Factory.StartNew(...) не возвращает активную задачу async, которая передается, а скорее "задача запуска Action", которая функционально всегда имеет taskStatus из "RanToCompletion". Поскольку мой код должен отслеживать статус задачи, чтобы увидеть, когда он станет "Отменен" (или "Неисправность" ), мне нужно использовать что-то вроде ниже:

var cts = new CancellationTokenSource();
Task t = Task.Run(
async () => {
while (true)
{
    cts.Token.ThrowIfCancellationRequested();
    try
    {
        "Running...".Dump();
        await Task.Delay(500, cts.Token);
    }
    catch (TaskCanceledException ex) { }
} }, cts.Token);

Task.Run(...), по желанию, возвращает сам процесс async, позволяющий мне получать фактические статусы "Отменено" или "Неисправность". Однако я не могу указать задачу как долгое время. Итак, кто-нибудь знает, как лучше всего запускать асинхронную задачу, одновременно сохраняя эту активную задачу (с нужным taskStatus) и долгое выполнение задачи?

4b9b3361

Ответ 1

Вызвать Unwrap в задаче, возвращенной из Task.Factory.StartNew, это вернет внутреннюю задачу, которая имеет правильный статус.

var cts = new CancellationTokenSource();
Task t = Task.Factory.StartNew(
async () => {
while (true)
{
    cts.Token.ThrowIfCancellationRequested();
    try
    {
        "Running...".Dump();
        await Task.Delay(500, cts.Token);
    }
    catch (TaskCanceledException ex) { }
} }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();

Ответ 2

У меня есть Task, которая циклически работает до тех пор, пока она не будет отменена извне, что я хотел бы установить как "длинный" (т.е. дать ему выделенный поток)... кто-нибудь знает, как лучше всего запускать асинхронную задачу, что сама активная задача (с желаемым статусом задачи) и долгое выполнение задачи?

Есть несколько проблем с этим. Во-первых, "длительная работа" не обязательно означает выделенный поток - это просто означает, что вы даете TPL подсказку о том, что задача длительная. В текущей (4.5) реализации вы получите выделенный поток; но это не гарантировано и может измениться в будущем.

Итак, если вам нужен выделенный поток, вам нужно просто создать его.

Другая проблема - понятие "асинхронная задача". Фактически с кодом async, запущенным в пуле потоков, является то, что поток возвращается в пул потоков, пока выполняется асинхронная операция (т.е. Task.Delay). Затем, когда async op завершается, поток берется из пула потоков, чтобы возобновить метод async. В общем случае это более эффективно, чем резервирование потока специально для выполнения этой задачи.

Итак, с async задачами, запущенными в пуле потоков, выделенные потоки на самом деле не имеют смысла.


Относительно решений:

Если вам нужен выделенный поток для запуска вашего кода async, я бы рекомендовал использовать AsyncContextThread из моей библиотеки AsyncEx

using (var thread = new AsyncContextThread())
{
  Task t = thread.TaskFactory.Run(async () =>
  {
    while (true)
    {
      cts.Token.ThrowIfCancellationRequested();
      try
      {
        "Running...".Dump();
        await Task.Delay(500, cts.Token);
      }
      catch (TaskCanceledException ex) { }
    }
  });
}

Однако вам почти наверняка не нужен выделенный поток. Если ваш код может выполняться в пуле потоков, то он, вероятно, должен; и выделенный поток не имеет смысла для методов async, запущенных в пуле потоков. Более конкретно, долговременный флаг не имеет смысла для методов async, запущенных в пуле потоков.

Другими словами, с помощью async lambda то, что пул потоков фактически выполняет (и видит как задачи), - это всего лишь части лямбда между операторами await. Поскольку эти части не долговечны, долговременный флаг не требуется. И ваше решение становится следующим:

Task t = Task.Run(async () =>
{
  while (true)
  {
    cts.Token.ThrowIfCancellationRequested(); // not long-running
    try
    {
      "Running...".Dump(); // not long-running
      await Task.Delay(500, cts.Token); // not executed by the thread pool
    }
    catch (TaskCanceledException ex) { }
  }
});

Ответ 3

В выделенном потоке нечего уступать. Не используйте async и await, используйте синхронные вызовы.

Этот вопрос дает два способа сделать спящий сон без await:

Task.Delay(500, cts.Token).Wait(); // requires .NET 4.5

cts.WaitHandle.WaitOne(TimeSpan.FromMilliseconds(500)); // valid in .NET 4.0 and later

Если часть вашей работы использует parallelism, вы можете запускать параллельные задачи, сохраняя их в массиве и использовать Task.WaitAny в Task[]. Все еще не используется для await в процедуре основного потока.

Ответ 4

Это необязательно, и Task.Run будет достаточным, поскольку планировщик заданий задает любую задачу LongRunning, если она работает более 0,5 секунды.

Вот почему. https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html

Вам нужно указать пользовательские TaskCreationOptions. Давайте рассмотрим каждый из варианты. AttachedToParent не должен использоваться в задачах async, поэтому вот. DenyChildAttach всегда должен использоваться с асинхронными задачами (подсказка: если вы этого не знали, тогда StartNew не является инструментом тебе нужно). DenyChildAttach передается Task.Run. HideScheduler может быть полезными в некоторых действительно неясных сценариях планирования, но в целом следует избегать для асинхронных задач. Это оставляет только LongRunning и PreferFairness, которые являются как подсказками оптимизации, которые должны быть указанных после профилирования приложения. Я часто вижу использование LongRunning в частности. В подавляющем большинстве ситуаций поток отрегулируйте любую долговременную задачу за 0,5 секунды - без Флаг LongRunning. Скорее всего, вам это действительно не нужно.

Ответ 5

Я думаю, что нужно учитывать не то, как долго работает поток, а сколько времени он действительно работает. В вашем примере есть короткая работа, и они await Task.Delay(...). Если это действительно так в вашем проекте, вы, вероятно, не должны использовать выделенный поток для этой задачи и позволять ему работать в обычном пуле потоков. Каждый раз, когда вы будете вызывать await для операции ввода-вывода или для Task.Delay() вы освобождаете поток для использования другими задачами.

Вы должны использовать LongRunning только LongRunning когда вы уменьшите свой поток из пула потоков и никогда не отдадите его или отдадите только в течение небольшого процента времени. В таком случае (когда работа длинная, а Task.Delay(...) сравнительно короткая) использование выделенного потока для работы является разумным решением. С другой стороны, если ваш поток действительно работает большую часть времени, он будет потреблять системные ресурсы (время ЦП), и, возможно, это не имеет значения, если он удерживает поток пула потоков, так как в любом случае предотвращает выполнение другой работы.,

Заключение? Просто используйте Task.Run() (без LongRunning) и используйте await в своей долгосрочной задаче, когда и если это возможно. LongRunning к LongRunning только тогда, когда вы действительно видите, что другой подход вызывает у вас проблемы, и даже тогда проверяйте ваш код и дизайн, чтобы убедиться, что он действительно необходим, и нет ничего, что вы могли бы изменить в своем коде.

Ответ 6

Реальная проблема, с которой вы здесь столкнулись, заключается в том, что ваша операция на самом деле не работает долго. Фактическая работа, которую вы выполняете, - это асинхронная операция, то есть она немедленно вернется к вызывающей стороне. Таким образом, вам не только не нужно использовать подсказку, выполняемую по расписанию, но и не нужно даже использовать поток пула потоков для выполнения этой работы, потому что он будет в основном мгновенным. Вы не должны использовать StartNew или Run вообще, не говоря уже о флаге длительного запуска.

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

Таким образом, ваш код упрощается вплоть до:

var cts = new CancellationTokenSource();
Task t = DoWork();
async Task DoWork()
{
    while (true)
    {
        cts.Token.ThrowIfCancellationRequested();
        try
        {
            "Running...".Dump();
            await Task.Delay(500, cts.Token);
        }
        catch (TaskCanceledException) { }
    }
}