.NET: Best way to execute a lambda on UI thread after a delay?

后端 未结 2 471
予麋鹿
予麋鹿 2021-01-01 22:04

I had a situation come up that required running a lambda expression on the UI thread after a delay. I thought of several ways to do this and finally settled on this approach

2条回答
  •  孤街浪徒
    2021-01-01 22:59

    I think what you've got is pretty good Scott.

    The only slight issue I think some might have with it, is that you're blocking a thread in order to execute your delay. Of course it's a background thread, and unlikely to cause problems unless you execute a lot of these calls concurrently (each tying up a thread), but it's still probably suboptimal.

    I would instead suggest that you factor the algorithm into a utility method, and avoid using Thread.Sleep.

    There's obviously probably innumerable ways of doing this, but here's one:

    public static class UICallbackTimer
    {
        public static void DelayExecution(TimeSpan delay, Action action)
        {
            System.Threading.Timer timer = null;
            SynchronizationContext context = SynchronizationContext.Current;
    
            timer = new System.Threading.Timer(
                (ignore) =>
                {
                    timer.Dispose();
    
                    context.Post(ignore2 => action(), null);
                }, null, delay, TimeSpan.FromMilliseconds(-1));
        }
    }
    

    To use:

        UICallbackTimer.DelayExecution(TimeSpan.FromSeconds(1),
            () => textBlock.Text="Done");
    

    Of course you could also write an implementation of this DelayExecution method which uses other types of timer such as the WPF DispatcherTimer or the WinForms Timer class. I'm not sure what the tradeoffs of these various timers would be. My guess would be DispatcherTimer's and WinForm's timers would actually still function on applications of the opposite type.

    EDIT:

    Re-reading my answer, I think actually I would be tempted to factor this into an extension method which works on synchronization contexts - if you think about it, a more general statement would be that you need to be able to post work back to a synchronization context after a certain delay.

    The SynchronizationContext already has a post method for queueing work, which the original caller does not want to block on completion. What we need is a version of this that posts the work after a delay, so instead:

    public static class SyncContextExtensions
    {
        public static void Post(this SynchronizationContext context, TimeSpan delay, Action action)
        {
            System.Threading.Timer timer = null;
    
            timer = new System.Threading.Timer(
                (ignore) =>
                {
                    timer.Dispose();
    
                    context.Post(ignore2 => action(), null);
                }, null, delay, TimeSpan.FromMilliseconds(-1));
        }
    }
    

    and use:

            SynchronizationContext.Current.Post(TimeSpan.FromSeconds(1),
                () => textBlock.Text="Done");
    

提交回复
热议问题