Winforms中类似于JavaScript的setTimeout函数

27
有没有简单的解决方案/想法/策略,可以在WinForms应用程序中创建一个setTimeout等效函数。我主要是网页开发人员,但不确定如何在WinForms应用程序中实现这一点。基本上,我有一个文本框,每次按键后我都想运行一个任务来填充列表(类似于自动完成),但希望能够取消(例如clearTimeout),如果用户继续输入字符...
我的唯一猜测是可能使用BackGroundWorker并使其最初处于睡眠状态,当它处于睡眠状态时,它可以被取消,如果用户停止输入键并且睡眠期结束,则它会运行任务等等
(我不在乎示例是C#还是Vb.Net)

JavaScript中的SetTimeout、SetInterval和ClearInterval在C#中的等效物 - Koray
10个回答

58

2
事情已经发生了很大的变化……感谢您发布答案! - davidsleeps

17

你可以使用一个System.Timers.Timer:将AutoReset设置为false,使用Start/Stop方法,并创建一个处理Elapsed事件的处理程序。

以下是在vb.net中实现的示例:

  Public Sub SetTimeout(act As Action, timeout as Integer)
    Dim aTimer As System.Timers.Timer
    aTimer = New System.Timers.Timer(1)
    ' Hook up the Elapsed event for the timer. 
    AddHandler aTimer.Elapsed, Sub () act
    aTimer.AutoReset = False
    aTimer.Enabled = True
  End Sub 

11
    public void setTimeout(Action TheAction, int Timeout)
    {
        Thread t = new Thread(
            () =>
            {
                Thread.Sleep(Timeout);
                TheAction.Invoke();
            }
        );
        t.Start();
    }

11
我不建议使用这种方法。为了执行一个简单的操作就创建一个新的线程吗?这样做有些过度了。在其他方法中,可以使用提供的Timer类来编写更加适合生产的代码。根据这个答案,每创建一个新线程就会分配1 MB 的内存。https://dev59.com/lU3Sa4cB1Zd3GeqPvoP4#2744464 -- 请记住,原帖中说会在每次按键后执行此操作...因此要考虑每个按键都需要分配1 MB 的内存和创建一个新线程。这是一个坏主意。 - marknuzz
不仅是一个坏主意。这可能会有并发问题。 - Sarsaparilla

11

计时器实现:

public void SetTimeout(Action action, int timeout)
{
    var timer = new System.Windows.Forms.Timer();
    timer.Interval = timeout;
    timer.Tick += delegate (object sender, EventArgs args)
    {
        action();
        timer.Stop();
    };
    timer.Start();
}

这不包括取消。 - Sahin

7
我可以提出以下建议:
internal class Timeout : System.Timers.Timer
{
    public Timeout (Action action, double delay)
    {
        this.AutoReset = false;
        this.Interval = delay;
        this.Elapsed += (sender, args) => action();
        this.Start();
    }
}
// Example
var timeout = new Timeout(() => {
    Console.WriteLine("init 1");
}, 500);
timeout.Stop();

2

当使用Task.Delay()并且您的操作是编辑/设置WinForms控件时,您必须添加TaskScheduler.FromCurrentSynchronizationContext(),否则会出现跨线程操作错误。

void SetTimeout(Action action, int ms)
{
    Task.Delay(ms).ContinueWith((task) =>
    {
        action();
    }, TaskScheduler.FromCurrentSynchronizationContext());
}           

SetTimeout(() => {
    myButton.Enabled = true;
}, 3000);  

2
你可以使用"最初的回答",还可以使用以下内容:
Delay.Do(3000 /*in ms*/, () => { /* Do somthing */ });

Where Delay.Do is:

using System;
using System.Timers;

public class Delay
{
    public static void Do(int after, Action action)
    {
        if (after <= 0 || action == null) return;

        var timer = new Timer { Interval = after, Enabled = false };

        timer.Elapsed += (sender, e) =>
        {
            timer.Stop();
            action.Invoke();
            timer.Dispose();
            GC.SuppressFinalize(timer);
        };

        timer.Start();
    }
}

注意: 当在UI线程更新控件时,请使用Control.Invoke:

最初的回答

Delay.Do(2000, () => { lblOk.Invoke((MethodInvoker)(() => { lblOk.Visible = false; })); });

1
这是我的方法,使用C# 7.0语法特性。 与js不同的是,当超时操作执行后将无法清除。
internal static class JsStyleTimeout
{
    private static readonly ConcurrentDictionary<int, Thread> InnerDic;

    private static int _handle;

    static JsStyleTimeout()
    {
        InnerDic = new ConcurrentDictionary<int, Thread>();
    }

    public static int Set(Action action, int delayMs)
    {
        var handle = Interlocked.Increment(ref _handle);

        var thread = new Thread(new ThreadStart(delegate
        {
            Thread.Sleep(delayMs);
            InnerDic.TryRemove(handle, out var _);
            Task.Factory.StartNew(action);
        }));
        InnerDic.TryAdd(handle, thread);

        thread.Start();
        return handle;
    }

    public static void Clear(int handle)
    {
        if (InnerDic.TryRemove(handle, out var thread))
            thread.Abort();
    }
}

0
    public void setTimeout(Action act, int timeout)
    {
        Action action = () =>
        {
            Thread.Sleep(Timeout);
            act();
        };

        new Thread(() => Invoke(action)).Start();
    }

0
我建议使用响应式编程来解决这个问题。请参考https://github.com/Reactive-Extensions/Rx.NET获取 .NET 的 Reactive Extensions,以及http://reactivex.io/获取有关响应式编程的一般信息。
很抱歉,我只熟悉 JavaScript 的响应式库,所以无法给您提供 C# 的示例,但在 JavaScript 中,它可能会像这样工作:
Rx.Observable.fromEvent(..eventdetails..)
    .debounceTime(300)
    .distinctUntilChanged()
    .subscribe(eventHandler);

使用这样的设置,您可以链接运算符以映射和合并来自源到订阅者的各种事件。上面的简单示例对事件(例如keyUp)做出反应,并等待300毫秒,直到没有新的keyUp,然后调用eventHandler,但仅在新值(300ms后)与上次发出的值不同的情况下才调用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接