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

发布于 2024-08-07 23:53:56 字数 411 浏览 3 评论 0原文

我想触发一个计时器在将来的某个时刻执行一次。为了代码简洁,我想使用 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 对象没有被释放。解决这个问题的最佳方法是什么?或者,我应该在这里采取完全不同的方法吗?

I want to fire off a timer to execute once at some point in the future. I want to use a lambda expression for code brevity. So I want to do something like...

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

I think it's pretty tidy. But in this case, the Timer object is not disposed. What is the best way to fix this? Or, should I be doing a totally different approach here?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

美人迟暮 2024-08-14 23:53:56

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

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

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));
}

That approach is flawed.
You are creating an object in memory with no reference to it. This means that the timer object is available to be garbage collected. While this code will work some of the time, you cannot predict when a garbage collection will kick in and remove the timer.

For example in the code below I force a garbage collection and it causes the timer to never fire.

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));
}
小糖芽 2024-08-14 23:53:56

这将实现你想要的,但我不确定它是最好的解决方案。我认为它简短而优雅,但可能比它的价值更令人困惑和难以理解。

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

This will accomplish what you want, but I am not sure its the best solution. I think its something that short and elegant, but might be more confusing and difficult to follow than its worth.

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
旧话新听 2024-08-14 23:53:56

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

bool fired = false;

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

GC.Collect();

Thread.Sleep(10000);

Assert.IsTrue(fired);

这可以在垃圾回收中幸存下来,因为您不必保留对任何内容的引用。

Instead of using a timer, leverage the thread pool instead:

bool fired = false;

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

GC.Collect();

Thread.Sleep(10000);

Assert.IsTrue(fired);

This survives garbage collection since you don't have to retain a reference to anything.

梦里的微风 2024-08-14 23:53:56

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

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();
    }
}

You could just wrap the timer class...

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();
    }
}
时光与爱终年不遇 2024-08-14 23:53:56

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

如果这是真的,你就不用担心。除非这段代码被多次调用,在这种情况下,您应该努力确定计时器的释放,这意味着您将保存一个计时器数组。

The timer object probably implements a destructor.
You can easily verify this in documentation or in the reflector.

If this is true, you shouldn't worry about it. Unless this piece of code gets called many times, in which case you should strive for deterministic deallocation of timers, meaning you would hold an array of timers, for example.

不醒的梦 2024-08-14 23:53:56

如果您有一个 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");
    }

它执行相同的操作,但要求在函数末尾发生等待。

由于您可能没有调度程序或想要使用它,但仍然想在不同时间安排多个操作,所以我会使用线程:

    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) 调用

If you have a Dispatcher and want to be in the UI (Dispatcher) thread, use this:

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

This function is not async because you did not want to wait within your function. This approach might be useful if you wanted to schedule more than one events at different times, but perhaps you really want the approach below:

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

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

Which does the same thing, but requires the await to happen at the end of your function.

Since you may not have a Dispatcher or want to use it, but still want to schedule multiple operations at different times, I would use a thread:

    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
    }

If you want to avoid async, I would recommend not using the threadpool and replacing the await Task.Delay(delayMs) call with a Thread.Sleep(delayMs) call

烛影斜 2024-08-14 23:53:56

您可以使用 TaskCompletionSource 例如:

static Task<T> ExecuteLater<T>(int delay, Func<T> func)
{
    var tcs = new TaskCompletionSource<T>();

    var timer = new System.Timers.Timer(delay) { AutoReset = false };
    timer.Elapsed += delegate { timer.Dispose(); tcs.SetResult(func()); };
    timer.Start();

    return tcs.Task;
}

并像这样调用它:

var result = await ExecuteLater<int>(5000, () => 50);

Or simply call:

 var result = await Task.Delay(5000).ContinueWith<int>((t) => { return 50; });

You could use TaskCompletionSource for example:

static Task<T> ExecuteLater<T>(int delay, Func<T> func)
{
    var tcs = new TaskCompletionSource<T>();

    var timer = new System.Timers.Timer(delay) { AutoReset = false };
    timer.Elapsed += delegate { timer.Dispose(); tcs.SetResult(func()); };
    timer.Start();

    return tcs.Task;
}

and call it like:

var result = await ExecuteLater<int>(5000, () => 50);

Or simply call:

 var result = await Task.Delay(5000).ContinueWith<int>((t) => { return 50; });
时光是把杀猪刀 2024-08-14 23:53:56
          System.Reactive.Linq.Observable.Interval(TimeSpan.FromSeconds(1))
            .FirstAsync()
            .Subscribe(_ => DoSomething()));
          System.Reactive.Linq.Observable.Interval(TimeSpan.FromSeconds(1))
            .FirstAsync()
            .Subscribe(_ => DoSomething()));
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文