.NET:延迟后在UI线程上执行lambda的最佳方法?

时间:2010-04-02 05:40:16

标签: c# .net lambda

我遇到了一个需要在延迟后在UI线程上运行lambda表达式的情况。我想到了几种方法,并最终确定了这种方法

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

但我想知道是否有一种更容易错过的方法。对更短,更简单或更简单的技术的任何建议?假设.NET 4可用。

2 个答案:

答案 0 :(得分:22)

我认为你所拥有的是非常好的斯科特。

我认为有些人可能会遇到的唯一一个小问题是你要阻止一个线程来执行你的延迟。当然它是一个后台线程,除非你同时执行大量的这些调用(每个都绑定一个线程),否则不太可能导致问题,但它仍然可能不是最理想的。

我建议您将算法分解为实用程序方法,并避免使用Thread.Sleep。

显然可能有无数种方法可以做到这一点,但这里有一个:

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

使用:

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

当然,您也可以编写此DelayExecution方法的实现,该方法使用其他类型的计时器,例如WPF DispatcherTimer或WinForms Timer类。我不确定这些不同计时器的权衡取舍。我的猜测是DispatcherTimer和WinForm的计时器实际上仍然可以在相反类型的应用程序上运行。

修改

重新阅读我的答案,我认为实际上我很想将其纳入一个适用于同步上下文的扩展方法 - 如果你考虑一下,更一般的说法就是你需要能够发布工作在一定延迟后回到同步上下文。

SynchronizationContext已经有一个用于排队工作的post方法,原始调用者不希望在完成时阻塞。我们需要的是一个版本,在延迟后发布工作,所以相反:

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

并使用:

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

答案 1 :(得分:2)

我认为最简单的方法是使用System.Windows.Forms.Timer,如果lambda不是一些随机函数。

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

如果不需要在循环中执行labda,请添加;

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

并致电

this.timer1.Start();

需要的地方。

另一种方法是使用Invoke方法。

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保证委托将在UI线程中执行(父窗口主描述符存在)

也许存在更好的变体。