.net 在未来某个时间调用单个操作的最佳方式是什么?

wgxvkvu9  于 2023-02-01  发布在  .NET
关注(0)|答案(8)|浏览(88)

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

sxissh06

sxissh061#

这种做法是有缺陷的。
您正在内存中创建一个对象,但没有对其进行引用。这意味着计时器对象可用于垃圾回收。虽然此代码在某些时候可以工作,但您无法预测垃圾回收何时启动并删除计时器。
例如,在下面的代码中,我强制执行垃圾收集,这会导致计时器永远不会触发。

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

niwlg2el2#

这将完成你想要的,但我不确定这是最好的解决方案。我认为这是一个简短和优雅的东西,但可能更令人困惑和难以遵循比它的价值。

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
piztneat

piztneat3#

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

bool fired = false;

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

GC.Collect();

Thread.Sleep(10000);

Assert.IsTrue(fired);

这在垃圾回收中仍然存在,因为您不必保留对任何内容的引用。

vfwfrxfs

vfwfrxfs4#

你可以 Package 这个计时器类...

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

iaqfqrcu5#

定时器对象 * 可能 * 实现了一个析构函数。你可以在文档或反射器中很容易地验证这一点。
如果这是真的,你就不用担心,除非这段代码被调用了很多次,在这种情况下,你应该争取确定性地释放定时器,这意味着你会持有一个定时器数组。

lskq00tm

lskq00tm6#

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

它做同样的事情,但是需要在函数结束时执行wait。
由于您可能没有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
    }

如果您希望避免异步,我建议不要使用线程池,并将await Task.Delay(delayMs)调用替换为Thread.Sleep(delayMs)调用

y53ybaqx

y53ybaqx7#

例如,可以使用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);

或直接调用:

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

ltqd579y8#

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

相关问题