是否有一个简单的解决方案/想法/策略来在 WinForms 应用程序中创建 setTimeout 等效函数。我主要是一名 Web 开发人员,但不确定如何在 WinForms 应用程序中进行此操作。基本上,我有一个文本框,每次击键后我想运行一个任务来填充列表(如自动完成类型的东西),但希望能够取消(例如clearTimeout)如果用户继续输入字符......
我唯一的猜测是也许使用 BackGroundWorker 并使其最初休眠,当它休眠时,它可以被取消,如果用户停止输入密钥并且休眠期结束,它就会继续运行任务等
(我不在乎示例是 C# 还是 Vb.Net)
我知道这是一个老问题,但替代解决方案是使用
Task.Delay(delay).ContinueWith((task) => { /* Code */ });
。
或者有
await Task.Delay(delay);
您可以使用 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
定时器实现:
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();
}
public void setTimeout(Action TheAction, int Timeout)
{
Thread t = new Thread(
() =>
{
Thread.Sleep(Timeout);
TheAction.Invoke();
}
);
t.Start();
}
我可以提出以下建议
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();
这是我的方式,使用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();
}
}
您还可以使用:
Delay.Do(3000 /*in ms*/, () => { /* Do somthing */ });
哪里
Delay.Do
是:
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; })); });
当使用
Task.Delay()
和您的操作来编辑/设置 winforms 控件时。您必须添加 TaskScheduler.FromCurrentSynchronizationContext()
否则会出现错误 Cross thread operation
void SetTimeout(Action action, int ms)
{
Task.Delay(ms).ContinueWith((task) =>
{
action();
}, TaskScheduler.FromCurrentSynchronizationContext());
}
SetTimeout(() => {
myButton.Enabled = true;
}, 3000);
public void setTimeout(Action act, int timeout)
{
Action action = () =>
{
Thread.Sleep(Timeout);
act();
};
new Thread(() => Invoke(action)).Start();
}
我建议为此使用反应式编程。请参阅 https://github.com/Reactive-Extensions/Rx.NET 了解 .NET 的响应式扩展,并参阅 http://reactivex.io/ 了解有关响应式编程的一般信息。
恐怕我只熟悉 JavaScript 反应式库,所以我无法给你一个 C-Sharp 示例,但在 JavaScript 中它的工作原理如下:
Rx.Observable.fromEvent(..eventdetails..)
.debounceTime(300)
.distinctUntilChanged()
.subscribe(eventHandler);
使用这样的设置,您可以链接运算符来映射和合并从源到订阅者的各种事件。上面的简单示例对事件(例如 keyUp)做出反应,并等待 300 毫秒内没有新的 keyUp,然后调用 eventHandler,但前提是新值(300 毫秒后)与上次发出的值不同。
从处理我们的时间事件的静态类开始是个好主意。我们可以将其命名为Timeman。
然后为每个以延迟开始的操作创建一个包。我们可以将其命名为TimemanEvent。
这是一个使用示例:
// 注册一个定时事件
var id = Timeman.SetTimeout(() => {
Console.log("代码已执行");
}, 3000);
// 或者
var id = Timeman.SetTimeout(MyMethod, 3000);
// 销毁事件
Timeman.ClearTimeout(id);
这是代码源:
/// <summary> Time Manager Utility </summary>
public class Timeman {
private static readonly ConcurrentDictionary<int, TimemanEvent> InnerDic;
static Timeman() {
InnerDic = new ConcurrentDictionary<int, TimemanEvent>();
}
/// <summary> Generate Unique Dictionary Id </summary>
private static int GetUniqueId() {
// If dict is empty take zero
if (InnerDic.Keys.Count == 0) return 0;
// If there is only one, take next
if (InnerDic.Keys.Count == 1) return InnerDic.Keys.First() + 1;
// Get all id numbers
var allKeys = InnerDic.Keys.ToList();
// Check the missing numbers in order.
allKeys.Sort();
var firstNumber = allKeys.First();
var lastNumber = allKeys.Last();
var missingNumbers = Enumerable.Range(firstNumber, lastNumber).Except(allKeys);
// If missing numbers are found, take the first one
if (missingNumbers.Count() > 0) return missingNumbers.First();
// Take next
return lastNumber + 1;
}
/// <summary>
/// Runs the method after a time(ms) has elapsed.
/// <code>
/// var id = Timeman.SetTimeout(() => {
/// Console.log("Code was executed");
/// }, 3000);
/// OR
/// var id = Timeman.SetTimeout(MyMethod, 3000);
/// </code>
/// </summary>
public static int SetTimeout(Action action, int delayMs) {
var uniqueId = GetUniqueId();
var te = new TimemanEvent(uniqueId, action, delayMs);
InnerDic.TryAdd(uniqueId, te);
te.Start();
return uniqueId;
}
/// <summary>
/// Removes the timeout before completion.
/// <code>
/// Timeman.ClearTimeout(id);
/// </code>
/// </summary>
public static void ClearTimeout(int id) {
if (InnerDic.TryRemove(id, out var tEvent))
tEvent?.Dispose();
}
}
internal class TimemanEvent {
private readonly Action action;
private readonly Timer timer;
private readonly int id;
public TimemanEvent(int id, Action action, int delayMs) {
this.id = id;
this.action = action;
this.timer = new Timer
{
Interval = delayMs
};
timer.Tick += OnTimerTick;
}
private void OnTimerTick(object sender, EventArgs e) {
action();
Timeman.ClearTimeout(id);
}
/// <summary> Stop timer and unregister events </summary>
internal void Dispose() {
timer.Tick -= OnTimerTick;
Stop();
}
internal void Start() => timer.Start();
internal void Stop() => timer.Stop();
}