Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

.net

lambda

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

Task.Factory.StartNew(() => Thread.Sleep(1000))
    .ContinueWith((t) => textBlock.Text="Done",TaskScheduler.FromCurrentSynchronizationContext());

But I'm wondering if there's an easier way that I missed. Any suggestions for a shorter, simpler or easier technique? Assume .NET 4 is available.

like image 787
Scott Bussinger Avatar asked Apr 02 '10 05:04

Scott Bussinger


2 Answers

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");
like image 62
Phil Avatar answered Sep 22 '22 15:09

Phil


I think the simplest way is using System.Windows.Forms.Timer, if lambda isnt some random function.

this._timer.Interval = 1000;
this._timer.Tick += (s, e) => this.textBlock.Text = "Done";

If labda has no need to be executed in the loop, add this;

this.timer1.Tick += (s, e) => this.timer1.Stop();

And call

this.timer1.Start();

where it needed.

Another way is using Invoke methodes.

delegate void FooHandler();

private void button1_Click(object sender, EventArgs e)
        {

            FooHandler handle = () =>  Thread.Sleep(1000); 
            handle.BeginInvoke(result => { ((FooHandler)((AsyncResult)result).AsyncDelegate).EndInvoke(result); this.textBox1.Invoke((FooHandler)(() => this.textBox1.Text = "Done")); }, null);
        }

Control.Invoke guarantees that delegate would be executed in the UI thread (where parent window main descriptor exists)

Maybe exists the better variant.

like image 20
Stremlenye Avatar answered Sep 20 '22 15:09

Stremlenye