Как создать поток / задачу с непрерывным циклом? - PullRequest
31 голосов
/ 19 сентября 2011

Я ищу правильный способ / структуру для создания цикла в Thread/Task ...

Причина этого заключается в том, что мне нужно каждые 15 секунд проверять БД на наличие отчетов.

Это то, что я пробовал до сих пор, но я получаю OutOfMemoryException:

    private void ViewBase_Loaded(object sender, RoutedEventArgs e)
{
    //On my main view loaded start thread to check report requests.
    Task.Factory.StartNew(() => CreateAndStartReportRequestTask());
}

private void CreateAndStartReportRequestTask()
{
    bool noRequest = false;

    do
    {
         //Starting thread to Check Report Requests And Generate Reports
         //Also need the ability to Wait/Sleep when there are noRequest.
         reportRequestTask = Task.Factory.StartNew(() => noRequest = CheckReportRequestsAndGenerateReports());

         if (noRequest)
         {
             //Sleep 15sec
             reportRequestTask.Wait(15000);
             reportRequestTask = null;
         }
         else
         {
             if (reportRequestTask.IsCompleted)
             {
                 reportRequestTask = null;
             }
             else
             {
                 //Don't want the loop to continue until the first request is done
                 //Reason for this is, losts of new threads being create in CheckReportRequestsAndGenerateReports()
                 //Looping until first request is done.
                 do
                 {

                 } while (!reportRequestTask.IsCompleted);

                 reportRequestTask = null;
             }
         }

    } while (true);
}

private bool CheckReportRequestsAndGenerateReports()
{
    var possibleReportRequest = //Some linq query to check for new requests

    if (possibleReportRequest != null)
    {
        //Processing report here - lots of new threads/task in here as well
        return false;
    }
    else
    {
        return true;
    }
}

Что я делаю не так?

Это правильный путь или я полностью отключен?

РЕДАКТИРОВАТЬ:

Самое главное, мой интерфейс все еще должен реагировать!

Ответы [ 4 ]

58 голосов
/ 19 сентября 2011

Примерно так будет работать:

var cancellationTokenSource = new CancellationTokenSource();
var task = Repeat.Interval(
        TimeSpan.FromSeconds(15),
        () => CheckDatabaseForNewReports(), cancellationTokenSource.Token);

Класс Repeat выглядит следующим образом:

internal static class Repeat
{
    public static Task Interval(
        TimeSpan pollInterval,
        Action action,
        CancellationToken token)
    {
        // We don't use Observable.Interval:
        // If we block, the values start bunching up behind each other.
        return Task.Factory.StartNew(
            () =>
            {
                for (;;)
                {
                    if (token.WaitCancellationRequested(pollInterval))
                        break;

                    action();
                }
            }, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
    }
}

static class CancellationTokenExtensions
{
    public static bool WaitCancellationRequested(
        this CancellationToken token,
        TimeSpan timeout)
    {
        return token.WaitHandle.WaitOne(timeout);
    }
}
21 голосов
/ 19 сентября 2011

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

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

private void ViewBase_Loaded(object sender, RoutedEventArgs e)
{
    // store this references as a private member, call Cancel() on it if UI wants to stop
    _cancelationTokenSource = new CancellationTokenSource();
    new Task(() => CreateAndStartReportRequestTask(), _cancelationTokenSource.Token, TaskCreationOptions.LongRunning).Start();
}

Затем в цепочке просмотра отчетов выполняйте цикл до тех пор, пока не будет установлено значение IsCancelRequested.Если работы нет, просто подождите на токене отмены 15 секунд (таким образом, если отменено, то проснется раньше).

private bool CheckReportRequestsAndGenerateReports()
{
    while (!_cancellationTokenSource.Token.IsCancelRequested) 
    {
        var possibleReportRequest = //Some linq query
        var reportRequestTask = Task.Factory.StartNew(() => noRequest = CheckReportRequestsAndGenerateReports(), _cancellationTokenSource.Token);

        if (noRequest)
        {
            // it looks like if no request, you want to sleep 15 seconds, right?
            // so we'll wait to see if cancelled in next 15 seconds.
            _cancellationTokenSource.Token.WaitHandle.WaitOne(15000);

        }
        else
        {
            // otherwise, you just want to wait till the task is completed, right?
            reportRequestTask.Wait(_cancellationTokenSource.Token);
        }
    }
}

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

     if (noRequest)
     {
         reportRequestTask.Wait(15000);
         reportRequestTask = null;
     }

Это вернется немедленно, а не ждать 15 секунд, потому что поток уже завершен.Переключение на токен отмены (или Thread.Sleep(), но тогда вы не можете так просто его прервать) даст вам необходимое ожидание обработки.

Надеюсь, это поможет, дайте мне знать, если яна мои предположения.

7 голосов
/ 10 февраля 2016

Я сделал обходной путь, начиная с ответа @ Роджера. (Мой друг тоже дал хорошие советы по этому поводу) ... Я копирую это здесь, я думаю, это может быть полезно:

/// <summary>
/// Recurrent Cancellable Task
/// </summary>
public static class RecurrentCancellableTask
{
    /// <summary>
    /// Starts a new task in a recurrent manner repeating it according to the polling interval.
    /// Whoever use this method should protect himself by surrounding critical code in the task 
    /// in a Try-Catch block.
    /// </summary>
    /// <param name="action">The action.</param>
    /// <param name="pollInterval">The poll interval.</param>
    /// <param name="token">The token.</param>
    /// <param name="taskCreationOptions">The task creation options</param>
    public static void StartNew(Action action, 
        TimeSpan pollInterval, 
        CancellationToken token, 
        TaskCreationOptions taskCreationOptions = TaskCreationOptions.None)
    {
        Task.Factory.StartNew(
            () =>
            {
                do
                {
                    try
                    {
                        action();
                        if (token.WaitHandle.WaitOne(pollInterval)) break;
                    }
                    catch
                    {
                        return;
                    }
                }
                while (true);
            },
            token,
            taskCreationOptions,
            TaskScheduler.Default);
    }
}
3 голосов
/ 13 декабря 2013

чувство авантюриста?

internal class Program
{
    private static void Main(string[] args)
    {
        var ct = new CancellationTokenSource();

        new Task(() => Console.WriteLine("Running...")).Repeat(ct.Token, TimeSpan.FromSeconds(1));

        Console.WriteLine("Starting. Hit Enter to Stop.. ");
        Console.ReadLine();

        ct.Cancel();

        Console.WriteLine("Stopped. Hit Enter to exit.. ");
        Console.ReadLine();
    }
}


public static class TaskExtensions
{
    public static void Repeat(this Task taskToRepeat, CancellationToken cancellationToken, TimeSpan intervalTimeSpan)
    {
        var action = taskToRepeat
            .GetType()
            .GetField("m_action", BindingFlags.NonPublic | BindingFlags.Instance)
            .GetValue(taskToRepeat) as Action;

        Task.Factory.StartNew(() =>
        {
            while (true)
            {
                if (cancellationToken.WaitHandle.WaitOne(intervalTimeSpan))
                    break;
                if (cancellationToken.IsCancellationRequested)
                    break;
                Task.Factory.StartNew(action, cancellationToken);
            }
        }, cancellationToken);
    }
}
...