如何正确地每分钟执行一次线程?

Posted

技术标签:

【中文标题】如何正确地每分钟执行一次线程?【英文标题】:How to execute threads every min correctly? 【发布时间】:2016-07-09 10:30:58 【问题描述】:

这个问题分为两部分。 我有一个控制台应用程序,它从多个服务器获取信息并将此信息保存到数据库。为了使这同时执行,我使用了线程。我试图让这个执行每分钟自动执行一次。

搜索***我发现这可以工作:

   var timer = new System.Threading.Timer((e) =>
        
            var models = ServerHandler.GetServerModels();

            foreach (var m in models)
            
                ServerHandler.MakeThreads(m);
            

            Console.WriteLine("Running...");
            Console.WriteLine("Press 'X' to exit or close the window, i : " + i);

            i++;


        , null, 0, TimeSpan.FromMinutes(1).Seconds);

但是这并没有按预期工作,它只执行一次。如果我更改为例如:

TimeSpan.FromMinutes(0.5).Seconds

或者:

TimeSpan.FromSeconds(30).Seconds

然后就可以了。 我做错了什么?

本题的第二部分:

当这实际上像我上​​面展示的那样工作时,会发生其他事情。 该进程连续运行,并在 474 个线程后崩溃并说系统内存不足。

我尝试为此使用线程睡眠,但是当我这样做时,它在运行一次后停止执行。

如果可能有帮助,请包括以下内容:

public static void MakeThreads(ServerModel model)

    Thread thread = new Thread(() => SaveServerInfo(model));
    thread.Start();
    //Thread.Sleep(1);
    //thread.Join();


我怎样才能做到这一点?

【问题讨论】:

'TimeSpan.FromMinutes(1).Seconds' 返回 0。你想要返回 60 的 'TimeSpan.FromMinutes(1).TotalSeconds'。 我会使用TimeSpan 重载。 msdn.microsoft.com/en-us/library/ah1h85ch(v=vs.110).aspx TimeSpan.FromSeconds(60).Seconds 怎么样?! 【参考方案1】:

在您的第一个问题中,使用 .Seconds 只会返回秒值,但您将分钟值定义为 0.5,因此秒将始终为零。

如果要返回需要使用 TotalSeconds 的秒数

TimeSpan.FromMinutes(0.5).TotalSeconds

在您使用的时间跨度内,您应该定义毫秒。因此,您将获得大量线程,因为它每 30 毫秒而不是每 30000 毫秒运行一次。

所以用

TimeSpan.FromMinutes(0.5).TotalMilliseconds

或者我总是觉得更容易的事情

(int)(1000 * 60 * 0.5)  // Then you just replace the 0.5 with the number of seconds. 

【讨论】:

试过了,它每秒运行一次,直到我有 475 个线程,然后它崩溃了 所以执行时间比间隔时间长。那是性能问题。你应该试试别的…… @Ra3IDeN 查看我更新的答案的第二部分。 好吧,我认为这实际上是我正在寻找的。虽然我很感激一个简单的澄清。我复制了您答案的第二部分,并认为执行将每 30 秒发生一次。我已经让应用程序运行了 3 分钟,它只执行了一次。我目前的代码: (int)TimeSpan.FromMinutes(1000 * 60 * 0.5).TotalSeconds) @CathalMF 老实说,我不知道您为什么还要费心将TimeSpan 转换为整数,而Timer 类已经接受TimeSpan。如果您按原样使用它,就不会产生混淆。【参考方案2】:

基本上,计时器完全按照它应该做的事情:每 0.5 秒运行一次代码。 :) 在你的情况下,这是一个问题......

(请检查语法错误等,我在记事本中写这个)

长期解决方案

您的问题似乎是您无法控制线程。以下是我的解决方法:(这个较长的解决方案显示了它或多或少的工作原理)

while (true)
    
    // we want to run it again in 0.5 seconds.
    DateTime start = DateTime.UtcNow.AddSeconds(0.5); 

    Thread[] threads = new Thread[models.Count];
    for (int i=0; i<models.Count; ++i)
    
        threads[i] = new Thread((a) => SaveServerInfo((ServerModel)a));
        threads[i].Start(models[i]);
    

    for (int i=0; i<models.Count; ++i)
    
        threads[i].Join();
    

    DateTime current = DateTime.UtcNow;
    if (current < start)
    
        Thread.Sleep(start.Subtract(current));
    


短解决方案

但是,这也可能会给您带来问题:您可能会产生太多线程。这可以通过一种称为线程池的机制来解决。事实证明,有一个简单的方法可以解决这个问题:

static void DoStuff(string s)

    // change to a value > 0.5 as well to ensure everything works
    Thread.Sleep(TimeSpan.FromSeconds(0.1)); 
    Console.WriteLine(s);


static void Handle(List<string> models)

    while (true)
    
        // we want to run it again in 0.5 seconds.
        DateTime start = DateTime.UtcNow.AddSeconds(0.5);

        Parallel.ForEach(models, (a) => DoStuff(a));
        DateTime current = DateTime.UtcNow;
        if (current < start)
        
            Thread.Sleep(start.Subtract(current));
        
    


static void Main(string[] args)

    List<string> models = new List<string>();
    for (int i = 0; i < 10; ++i)
    
        models.Add(i.ToString());
    

    Handle(models);


【讨论】:

这不起作用。一旦我没有在调试器中运行它,第一个解决方案就给了我一个超出范围异常的索引。第二种解决方案只运行线程一次然后停止。 @Ra3IDeN 我确实忘记了第一个示例中的线程启动;现在已经解决了。第二种解决方案按原样工作。如果您得到一个超出范围异常的索引,您正在更改线程中的集合 - 在任何情况下都是不允许的,您正在创建一个竞争条件! 至于您声称它没有不行,我刚刚创建了一个最小的测试用例,它工作得很好;请自行检查。

以上是关于如何正确地每分钟执行一次线程?的主要内容,如果未能解决你的问题,请参考以下文章

如何在java中实现 sql语15分钟执行一次 希望说详细一下

每隔几分钟轮询一次 gps 强度

如何从计数器更改UIimage视图中的图像

如何每 72 分钟执行一次 cron 任务

quartz CronTrigger 如何定义间隔65分钟执行一次表达式

如何让linux每隔五分钟进行一次ping命令