在将来的某个时间调用单个操作的最佳方法?

时间:2009-10-14 20:29:03

标签: c# .net lambda

我希望在未来的某个时刻启动计时器以执行一次。我想使用lambda表达式来简化代码。所以我想做点像......

(new System.Threading.Timer(() => { DoSomething(); },
                    null,  // no state required
                    TimeSpan.FromSeconds(x), // Do it in x seconds
                    TimeSpan.FromMilliseconds(-1)); // don't repeat

我觉得它很整洁。但在这种情况下,不会丢弃Timer对象。解决这个问题的最佳方法是什么?或者,我应该在这里做一个完全不同的方法吗?

7 个答案:

答案 0 :(得分:5)

这将完成你想要的,但我不确定它是最好的解决方案。我觉得它的东西短而优雅,但可能比它的价值更令人困惑和难以理解。

System.Threading.Timer timer = null;
timer = new System.Threading.Timer(
    (object state) => { DoSomething(); timer.Dispose(); }
    , null // no state required
    ,TimeSpan.FromSeconds(x) // Do it in x seconds
    ,TimeSpan.FromMilliseconds(-1)); // don't repeat

答案 1 :(得分:3)

这种做法存在缺陷 您正在内存中创建一个对象而没有引用它。这意味着计时器对象可用于垃圾回收。虽然此代码在某些时候会起作用,但您无法预测垃圾收集何时启动并删除计时器。

例如,在下面的代码中,我强制进行垃圾收集,这会导致计时器永远不会触发。

static void Main(string[] args)
{
    DoThing();
    GC.Collect();
    Thread.Sleep(5000);
}


static void DoThing()
{
    new System.Threading.Timer(x => { Console.WriteLine("Here"); },
            null,  
            TimeSpan.FromSeconds(1), 
            TimeSpan.FromMilliseconds(-1));
}

答案 2 :(得分:1)

你可以包装计时器类......

class Program
{
    static void Main(string[] args)
    {
        MyTimer.Create(
            () => { Console.WriteLine("hello"); },
            5000);
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.Read();
    }
}
public class MyTimer
{
    private MyTimer() { }
    private Timer _timer;
    private ManualResetEvent _mre;

    public static void Create(Action action, int dueTime)
    {
        var timer = new MyTimer();
        timer._mre = new ManualResetEvent(false);

        timer._timer = new Timer(
            (x) =>
            {
                action();
                timer._mre.Set();
            },
            null,
            dueTime,
            Timeout.Infinite
            );

        new Thread(new ThreadStart(() =>
        {
            timer._mre.WaitOne();
            timer._timer.Dispose();
        })).Start();
    }
}

答案 3 :(得分:1)

而不是使用计时器,而是利用线程池:

bool fired = false;

ThreadPool.RegisterWaitForSingleObject(new ManualResetEvent(false), 
    (state, triggered) =>
    {
        fired = true;
    }, 
    0, 9000, true);

GC.Collect();

Thread.Sleep(10000);

Assert.IsTrue(fired);

这可以保留垃圾收集,因为您不必保留对任何内容的引用。

答案 4 :(得分:0)

计时器对象可能实现析构函数。 您可以在文档或反射器中轻松验证。

如果这是真的,你不应该担心。除非这段代码被多次调用,否则你应该努力确定性地重新分配定时器,这意味着你将拥有一组定时器,例如。

答案 5 :(得分:0)

如果你有一个Dispatcher并希望进入UI(Dispatcher)线程,请使用:

    void MyNonAsyncFunction()
    {
        Dispatcher.InvokeAsync(async () =>
        {
            await Task.Delay(1000);
            MessageBox.Show("Thank you for waiting");
        });
    }

此功能不是异步的,因为您不想在功能中等待。如果您想在不同时间安排多个事件,这种方法可能很有用,但您可能真的想要以下方法:

    async void MyAsyncFunction()
    {
        // Do my other things

        await Task.Delay(1000);
        MessageBox.Show("Thank you for waiting");
    }

它做同样的事情,但需要等待在你的函数结束时发生。

由于您可能没有Dispatcher或想要使用它,但仍希望在不同时间安排多个操作,我会使用一个线程:

    static void MyFunction()
    {
        // Do other things...
        Schedule(1000, delegate
        {
            System.Diagnostics.Debug.WriteLine("Thanks for waiting");
        });
    }

    static void Schedule(int delayMs, Action action)
    {
#if DONT_USE_THREADPOOL
        // If use of threadpool is undesired:
        new System.Threading.Thread(async () =>
        {
            await Task.Delay(delayMs);
            action();
        }
        ).Start(); // No need to store the thread object, just fire and forget
#else
        // Using the threadpool:
        Task.Run(async delegate
        {
            await Task.Delay(delayMs);
            action();
        });
#endif
    }

如果您想避免异步,我建议不要使用线程池并用Task.Delay(delayMs)调用替换等待Thread.Sleep(delayMs)来电

答案 6 :(得分:-2)

          System.Reactive.Linq.Observable.Interval(TimeSpan.FromSeconds(1))
            .FirstAsync()
            .Subscribe(_ => DoSomething()));