开发者

Best way to call a single operation at some time in the future?

开发者 https://www.devze.com 2022-12-07 23:46 出处:网络
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...

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 prett开发者_如何学Cy 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?


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


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


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.


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


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.


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


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

精彩评论

暂无评论...
验证码 换一张
取 消