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

发布于 2024-08-27 15:16:42 字数 313 浏览 2 评论 0原文

我遇到了一种情况,需要在延迟后在 UI 线程上运行 lambda 表达式。我想了几种方法来做到这一点,最后决定采用这种方法

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

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

拥抱没勇气 2024-09-03 15:16:42

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

我认为有些人可能会遇到的唯一的小问题是,您为了执行延迟而阻塞了一个线程。当然,它是一个后台线程,除非您同时执行大量这些调用(每个调用都占用一个线程),否则不太可能导致问题,但它仍然可能不是最佳的。

相反,我建议您将算法分解为实用方法,并避免使用 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");

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");
煞人兵器 2024-09-03 15:16:42

我认为最简单的方法是使用 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 线程(父窗口主描述符存在)中执行,

也许存在更好的变体。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文