C#:如何在特定时间启动线程

42

我该如何在特定的时间(比如16:00)启动一个后台线程?

当应用程序启动时,该线程将等待到指定时间。但如果应用程序在指定时间之后启动,则该线程将立即运行。

ThreadPool.QueueUserWorkItem(MethodtoRunAt1600);


那个线程会做什么?如果您需要在特定时间运行某些作业,您应该查看http://www.quartz-scheduler.org/。 - treze
1
而.NET版本可以在此处找到:http://quartznet.sourceforge.net/ - Fredrik
请看这里,有几个有用的答案:https://dev59.com/6HA75IYBdhLWcg3wipiH - Jeroen van Langen
@FredrikRedin 感谢您的纠正。当然,我想发布一个链接到http://quartznet.sourceforge.net/。 - treze
这个定时任务需要支持取消吗? - Gusdor
10个回答

79

你可以在16:00设置一个定时器。我在这里回答过类似的问题。 那肯定会对你有所帮助。

private System.Threading.Timer timer;
private void SetUpTimer(TimeSpan alertTime)
{
     DateTime current = DateTime.Now;
     TimeSpan timeToGo = alertTime - current.TimeOfDay;
     if (timeToGo < TimeSpan.Zero)
     {
        return;//time already passed
     }
     this.timer = new System.Threading.Timer(x =>
     {
         this.SomeMethodRunsAt1600();
     }, null, timeToGo, Timeout.InfiniteTimeSpan);
}

private void SomeMethodRunsAt1600()
{
    //this runs at 16:00:00
}

然后使用以下方式进行设置

SetUpTimer(new TimeSpan(16, 00, 00));

编辑:保留 Timer 的引用,因为无论 Timer 是否活动,它都会受到垃圾回收的影响。


@SriramSakthivel 你好,能否在三个不同的时间调用该方法? - VAAA
@VAAA 设置3个不同的定时器或使用单个定时器,一旦它被触发,设置第二个与相同的定时器,以此类推。 - Sriram Sakthivel
1
@Reynan 不是这样的。要将其设置为每天,您需要在Timer构造函数的最后一个参数中传递Timeout.FromDays(1)而不是Timeout.InfiniteTimeSpan。还请注意,如果时间已经过去,则可能需要更改跳过触发计时器的if语句。 - Sriram Sakthivel
@jannagy02想要每天在同一时间运行。这种情况下,Timeout.FromDays(1)是他需要的。 - Sriram Sakthivel
1
@LeonardAB 我建议使用Windows任务计划程序或Quartz任务计划程序。 - Sriram Sakthivel
显示剩余5条评论

12

我会使用类似于Quartz的作业调度库,或者简单地创建控制台应用程序,并在特定时间运行它,使用Windows任务计划程序。

为什么不直接使用System.Timers.Timer?

  • 定时器没有持久性机制。
  • 定时器具有不灵活的调度(只能设置开始时间和重复间隔,无法基于日期、每天的时间等进行设置)。
  • 定时器不利用线程池(一个定时器对应一个线程)。
  • 定时器没有真正的管理方案 - 您需要编写自己的机制来记住、组织和检索您的任务名称,等等。

7
为什么要安装整个库来完成使用“Timer”轻松实现的事情? - jgauffin
1
为什么要安装整个库,或者创建然后清理定时器,只为了一个两行代码的一次性“等待”机制 :| - Timothy Khouri
2
@jgauffin:假设你想要定一个闹钟在早上4点响起。在下午4点时,你设置了一个12小时的计时器。在凌晨2点时,计时器还有2个小时就要响了。但是这一天是“退回”时间,时钟被调回了1个小时。所以现在是早上1点,计时器将在早上3点响起。同样的事情也会在“前进”时间发生。计时器将在早上5点响起,因为时钟被向前移动了。关键是系统时间是正确的。错误在于假设12小时的实际时间总是等于12小时的时钟时间。 - Jim Mischel
2
@jgauffin:你不能为System.Timers.Timer或其他任何.NET计时器设置特定时间。你只能为一段时间设置它们。 - Jim Mischel
2
@Damith:关于计时器不使用线程池的观点是不正确的。计时器只有在执行其回调函数时才占用一个线程,并且它在池线程上执行。如果您创建了100个计时器,就不会像分配100个线程那样。 - Jim Mischel
显示剩余6条评论

9

您可以使用定时器来完成:

public class OncePerDayTimer : IDisposable
{
    private DateTime _lastRunDate;
    private TimeSpan _time;
    private Timer _timer;
    private Action _callback;

    public OncePerDayTimer(TimeSpan time, Action callback)
    {
        _time = time;
        _timer = new Timer(CheckTime, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
        _callback = callback;
    }

    private void CheckTime(object state)
    {
        if (_lastRunDate == DateTime.Today)
            return;

        if (DateTime.Now.TimeOfDay < _time)
            return;

        _lastRunDate = DateTime.Today;
        _callback();
    }

    public void Dispose()
    {
        if (_timer == null)
            return;

        _timer.Dispose();
        _timer = null;
    }
}

这将在夏令时变化面前失败。它可能会晚一个小时或早一个小时执行。 - Jim Mischel
1
@JimMischel:不会的。它检查的是时间 - jgauffin
我的错误。我现在明白你正在轮询。 - Jim Mischel
3
这个程序每秒钟进行一次轮询吗?对于每天只运行一次的进程来说,这似乎有些过度。 - JMG
4
@JMG:你的CPU周期这么少吗,以至于这很重要吗? - jgauffin

6
任何依赖于.NET Framework中已存在的System.Timers.TimerSystem.Threading.Timer或其他计时器的解决方案,在夏令时改变面前都将失败。如果您使用这些定时器之一,您将不得不进行一些轮询。
Windows有一个可等待定时器,您可以使用它,但它没有被任何Framework类支持。我几年前为其编写了一个包装器。我发表的文章已经不再可用,但您可以从http://www.mischel.com/pubs/waitabletimer.zip下载完整的源代码。
话虽如此,如果您的程序唯一要做的事情是每天运行一次,或者如果它执行的任务可以从程序的其余部分中分离出来,那么您几乎肯定会更喜欢计划任务。尽管我从未使用过Quartz.NET,但我毫不犹豫地推荐它,基于我信任的人给出的好评。

2
一个非常简单的解决方案:
if (DateTime.Now.Hour > 16)
{
    MethodtoRunAt1600();
}
else
{
    var next16 = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 16, 0, 0);
    var timer = new Timer(MethodtoRunAt1600, null, next16 - DateTime.Now, TimeSpan.FromHours(24));
    timer.Start()
}

1
如果您想要一些“快速而简单”的东西……那么您可以在线程池中启动您的线程,并“等待”合适的时间。
// By the way, this code would be *INSIDE* of the background thread. That's what
// the sentence above says, but apparently we devs only read code :)
while (DateTime.Now.TimeOfDay < myDesiredStartTime)
{
    Thread.Sleep(1000);
}

12
嘲笑有人想从UI线程调用那个... - Martin Milan
1
这是最简单的答案 - 不要让主线程决定何时启动后台线程,而是立即启动后台线程,并让它决定何时执行其功能。 - mbeckish
1
@MartinMilan - 不,你不会从UI线程调用它。显然你没有读到上面代码片段的那个非常简短的句子 :) - Timothy Khouri
4
不,Timothy,我没有暗示你会支持这个做法,只是我能想象有人(经验不足的人)会尝试...... Timothy,请理解幽默感...... - Martin Milan

1
这是一种简单的方法,可以安排一个方法被调用一次,可以立即执行或在16:00执行。
var runAt = DateTime.Today + TimeSpan.FromHours(16);
if(runAt < DateTime.Now)
{
    MethodtoRunAt1600();    
}
else
{
    var dueTime = runAt - DateTime.Now;
    var timer = new System.Threading.Timer(_ => MethodtoRunAt1600(), null, dueTime, TimeSpan.Zero); 
} 

0

我们可以这样使用吗?

DispatcherTimer timer ;
// When application is started
if (DateTime.Now.Hour == 16)
{
   // Start your task
}
else
{
    timer = new DispatcherTimer();
    timer.Interval = new TimeSpan(0, 1, 0);
    timer.Tick += timer_Tick;
    timer.Start();
}

然后每隔一分钟检查一次...

void timer_Tick(object sender, EventArgs e)
{
    if (DateTime.Now.Hour == 16)
    {
        // Start your task
    }
}

1
我意识到这已经过了几年,但是,在一个小时内,这不会运行任务60次(每分钟一次)吗? - DDuffy

0


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接