Can I use Task.Delay as a timer?

27
我希望每秒都能执行一些代码。我现在使用的代码是:

Task.Run((Action)ExecuteSomething);

ExecuteSomething() 定义如下:
 private void ExecuteSomething()
        {
            Task.Delay(1000).ContinueWith(
               t =>
               {
                   //Do something.

                   ExecuteSomething();
               });
        }

这个方法会阻塞线程吗?还是我应该在C#中使用Timer类?而且似乎 Timer 也专门为执行分配了一个单独的线程(?)


3
关于“独立线程”,所有计时器之间只有一个共享的线程。 - Stephen Cleary
4个回答

37

Task.Delay 内部使用 Timer

使用 Task.Delay 可以比使用 Timer 让您的代码更加清晰。而且使用 async-await 也不会阻塞当前线程(通常是 UI 线程)。

public async Task ExecuteEverySecond(Action execute)
{
    while(true)
    {
        execute();
        await Task.Delay(1000);
    }
}

源代码中的:Task.Delay

// on line 5893
// ... and create our timer and make sure that it stays rooted.
if (millisecondsDelay != Timeout.Infinite) // no need to create the timer if it's an infinite timeout
{
    promise.Timer = new Timer(state => ((DelayPromise)state).Complete(), promise, millisecondsDelay, Timeout.Infinite);
    promise.Timer.KeepRootedWhileScheduled();
}

// ...

1
从性能方面来看,使用Task.Delay是否会对每次创建新计时器产生影响? - Joel Harkes
@JoelHarkes,影响将取决于使用情况,通常在所有性能方面的问题中都是如此;) - Fabio

9

Microsoft的响应式框架非常适合这个需求。只需要使用NuGet“System.Reactive”获取相关内容,然后您就可以这样做:

IDisposable subscription =
    Observable
        .Interval(TimeSpan.FromSeconds(1.0))
        .Subscribe(x => execute());

当您想要停止订阅时,只需调用subscription.Dispose()。此外,响应式框架比任务或基本计时器提供更强大的功能。


2
在我的情况下,使用 Rx 与使用 Task.Delay 相比是否具有特定的优势?只是想知道是否值得为此目的包含该软件包。无论如何,已经点赞了。 - Sharun
1
@Sharun - Rx的功能与LINQ相当甚至更强大。根据您所描述的情况,似乎您并不需要更多的功能,但我认为您实际上会从Rx提供的功能中受益。您能否更详细地描述一下您想要做什么? - Enigmativity

2
你能使用Task.Delay作为计时器吗?这取决于你是否需要等待精确的时间间隔,或者只需要在其他工作完成后等待指定的时间间隔。
换句话说,我过去经常在服务的一部分中使用计时器来获取或推送数据以进行同步。问题是,如果你每60秒获取一次数据,但数据库变慢并且检索超过了60秒。现在你有一个问题,因为你将会获取相同的数据两次。所以你更新了流程,停止计时器直到它完成,然后再重新启动计时器。这很好用,但使用Task.Delay可以为你完成这项工作。
因此,如果你只需要在执行之间延迟一段时间,那么Task.Delay完美地解决了这个问题。

我使用了System.Threading.Timer,并使用Change(delay, -1)来获取数据后重新启动计时器。这样可以避免重复,但需要在任何时候重新启动计时器,特别是在出现异常情况下。 - Arci

0
static class Helper
{
    public async static Task ExecuteInterval(Action execute, int millisecond, IWorker worker)
    {
        while (worker.Worked)
        {
            execute();

            await Task.Delay(millisecond);
        }
    }
}


interface IWorker
{
    bool Worked { get; }
}

请注意,如果execute的运行时间与延迟相比具有相当大的影响,那么这将会漂移,即不遵守周期间隔。 - Timo
2
@Timo 人们可以考虑在 .NET 6 中使用 PeriodicTimer 来解决这个漂移问题。 - prospector

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