【问题标题】:How to create a thread/Task with a continuous loop?如何创建具有连续循环的线程/任务?
【发布时间】:2011-09-19 13:49:42
【问题描述】:

我正在寻找在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;
    }
}

我做错了什么?

这是正确的方式还是我完全关闭了?

编辑:

最重要的是,我的 UI 必须仍然是响应式的!

【问题讨论】:

  • 不是将循环放置在此任务中,而是在无限循环中创建任务。
  • 您正在创建谁知道有多少任务。您的代码没有多大意义。您可能应该编辑和描述您想要完成的确切目标
  • 谁告诉你的进程何时结束?该程序?还是任务处理本身(即一旦你得到 FALSE 返回)?
  • 值得注意的是,2021年的最佳实践是使用Task.Run而不是Task.Factory.StartNew

标签: c# .net multithreading thread-safety multitasking


【解决方案1】:

这样的事情会起作用:

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);
    }
}

【讨论】:

  • @Roger:我喜欢你的重复课,我想知道你是否应该添加一个选项来创建任务作为 TaskCreationOptions.LongRunningTask 以避免在线程池中占用线程
  • @Roger:介意我将您的课程添加到我工作的技巧包中吗?我们有一个非常强大的共享组件委员会,我们尝试创建通用的、可重用的实用程序类,而这个类非常通用(从最好的意义上来说)。
  • @RogerLipscombe 纯粹的好奇心:for(;;) 怎么会兴奋一阵子(真的)?
  • @mateuscb 从我的 C/C++ 时代开始,当 while(true) 经常发出警告时——“条件始终不变”或类似的东西。我想。
  • ...另外,我发现它更易于阅读。在 while(true) 中,您必须实际查看“true”一词。在 for (;;) 中,整件事读作“永远”。
【解决方案2】:

听起来你想要这样的东西。如果我误解了您的意图,请纠正我...

首先,在开始时,将其设置为长时间运行的任务,这样它就不会消耗线程池中的线程,而是创建一个新的...

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(),但您不能轻易中止它)将为您提供所需的处理等待。

希望这会有所帮助,如果我的假设有误,请告诉我。

【讨论】:

  • 谢谢詹姆斯。这听起来很对。请快速阅读我的更新。我添加了一些 cmets。
  • @Willem:我阅读了您的更新,我认为我的循环正确。这基本上会将报告检查器作为一个长时间运行的任务(线程)启动,并且在内部,如果没有创建工作,它将等待 15 秒(或取消,以先到者为准),然后重试。如果有工作,它会等待该工作完成然后重复(直到取消)。
  • 谢谢詹姆斯。似乎一切正常。最后一个问题。在CheckReportRequestsAndGenerateReports() 中,我有一个linq 查询需要访问主thread。我打电话给this.Dispatcher.Invoke((Action)(() => { //some linq query })); 第一个请求工作正常,但只是在第二个请求上停止应用程序。知道我做错了什么吗?
  • @Willem:嗯,需要查看代码,您能否在问题的更新中发布它的样本?
  • @Willem:太好了!调度有什么问题?
【解决方案3】:

我已经从@Roger 的回答开始解决了。 (我的一个朋友也对此提出了很好的建议)......我在这里复制它我想它可能有用:

/// <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);
    }
}

【讨论】:

  • 对于我目前的轻量级任务来说,这是完美的。谢谢你:)
  • 你不觉得当while 总是正确的时候使用do-while 是丑陋的吗?
【解决方案4】:

感觉很冒险?

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);
    }
}

【讨论】:

  • 我认为这不是一个好主意。最大的问题是要重复的任务不会表现得像在循环中一样。使用该代码,将并行生成多个实例,而无法控制同时运行的实例数量。为了使循环正常工作,每次迭代必须仅在前一个迭代完成后开始。此外,while(true) 正在消耗 ThreadPool 中的一个线程,该线程基本上站在那里等待开始一个新任务。
  • 不需要流水线。 OP 只想每 X 秒运行一次任务 :)
猜你喜欢
  • 2016-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-01
  • 1970-01-01
  • 2014-03-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多