在.NET中实现定时回调的最简单方法是什么?

4

我希望在C#(更具体地说,是WPF)中实现类似以下的功能:

Thread.Invoke(MyCallback, 1000);

一秒钟后,将会调用MyCallback函数一次。

在.NET中,最简单的方法是什么?我需要设置一个计时器并挂接事件吗?


最近是否有大量与“计时器”相关的问题,还是只有我这样感觉? - Yuck
6个回答

6
您可以使用 System.Timers.Timer 来实现此操作而无需创建自己的线程。 实现Elapsed回调来执行您想要的操作,将Enabled设置为true,将AutoReset设置为false以实现单次调用。

确保在使用完 Timer 对象后进行Dispose处理!


我很感兴趣看到将此封装为一个接受操作和延迟的方法的代码。 - George Duckett
抱歉,@George,我手头没有这样的封装程序。 - Steve Townsend
@George,我和至少另一个回答者已经发布了可能的实现方案。 - Jay
@Jay,非常感谢你的回复。当我发表评论时还没有任何回复。 :) - George Duckett

3
这是一个使用计时器在一定时间后运行操作的方法:
static void Invoke(TimeSpan dueTime, Action action)
{
    Timer timer = null;
    timer = new Timer(_ => { timer.Dispose(); action(); });
    timer.Change(dueTime, TimeSpan.FromMilliseconds(-1));
}

我不确定计时器有多轻量级,但它应该比阻塞线程池线程更好。

用法:
Invoke(TimeSpan.FromSeconds(5), () =>
{
    Console.WriteLine("Hello World");
});

2
Task.Factory.StartNew(() =>
{
    Thread.Sleep(1000);
    // Do Stuff
});

如下评论所述,虽然这种方法易于理解且书写简短,但相对来说效率较低,会消耗更多资源。


我认为这并不糟糕,它不会真正使用太多资源,而且线程等待也不会使用CPU时间。我猜计一个计时器会使用更多的资源。 - George Duckett
3
我有一个实用的包装器,它使用System.Threading.Timer,但这是一种相当清晰的方式来做到这一点。正如Kirk提到的那样,它会阻止线程池线程,因此如果您在许多地方使用此模式,则不希望使用它。在最坏的情况下,您可能会使线程池耗尽资源(特别是如果您有长时间的延迟),您的回调将比您预期的执行得更晚。 - Dan Bryant
3
即使是空闲的线程默认使用1MB的虚拟内存;浪费地址空间没有任何意义。而且由于线程池线程总量有限,让一个线程闲置一秒钟没有做任何事情也没有意义,因为它可以做其他事情。 - Tim Robinson
@Dan,没错,我没有考虑到线程池会用完线程等问题。Timer使用Windows消息或其他方式来避免这种情况(使用线程)吗? - George Duckett
好的,@丹、@蒂姆、@柯克。我承认错误,我的答案绝对不是最好的方法(将编辑答案)。幸运的是,OP要求最简单的方法。 :P - George Duckett
@George,操作系统通常具有特殊支持注册计时器回调函数的功能,因为这在程序中是如此常见的需求。.NET BCL 也有一个定时器,它使用消息泵,但其主要目的只是确保回调发生在 UI 线程上。 - Dan Bryant

0

使用方法:

Delay.Invocation(MyCallback, 1000);

//or

Delay.Invocation(() => MyCallbackWithArgs(arg1, arg2), 1000);

包装器:

public class Delay
{
    readonly Timer _timer;
    readonly Action _action;

    private Delay(Action action, double delayMilliseconds)
    {
        _action = action;
        _timer = new Timer(delayMilliseconds);
        _timer.Elapsed += ExecuteCallback;
    }

    void ExecuteCallback(object sender, ElapsedEventArgs e)
    {
        _timer.Stop();
        _timer.Elapsed -= ExecuteCallback;
        _timer.Dispose();

        _action();
    }

    void Begin()
    {
        _timer.Start();
    }

    public static void Invocation(Action action, int delayMilliseconds)
    {
        var delay = new Delay(action, delayMilliseconds);
        delay.Begin();
    }
}

0

只是我的个人意见,

public class WaitableWorker
    {
        private readonly System.Threading.Timer timer;
        public WaitableWorker(int interval, Action callback, bool blocking = false)
        {
            if (blocking)
            {
                Thread.Sleep(interval);
                callback();
            }
            else
            {
                timer = new System.Threading.Timer(_ =>
                                                       {
                                                           timer.Dispose();
                                                           callback();
                                                       },
                                                   null, interval, Timeout.Infinite);
            }
        }

    }

使用方法

 WaitableWorker worker=new WaitableWorker(3000,DoWork);

0

如果你有响应式扩展, 你可以这样做:

Observable.Return(true)
    .Delay(TimeSpan.FromSeconds(1))
    .Subscribe(_ => DoWork());

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