c#语言中怎么实现延时功能,timer-在C#中创建“一次运行”延时功能的最佳方法...

timer-在C#中创建“一次运行”延时功能的最佳方法

我正在尝试创建一个接受Action和Timeout并在Timeout之后执行Action的函数。 该功能是非阻塞的。 该函数必须是线程安全的。 我也非常非常想避免Thread.Sleep()。

到目前为止,我能做的最好的事情是:

long currentKey = 0;

ConcurrentDictionary timers = new ConcurrentDictionary();

protected void Execute(Action action, int timeout_ms)

{

long currentKey = Interlocked.Increment(ref currentKey);

Timer t = new Timer(

(key) =>

{

action();

Timer lTimer;

if(timers.TryRemove((long)key, out lTimer))

{

lTimer.Dispose();

}

}, currentKey, Timeout.Infinite, Timeout.Infinite

);

timers[currentKey] = t;

t.Change(timeout_ms, Timeout.Infinite);

}

问题是从回调本身调用Dispose()可能不好。 我不确定“终止”是否安全,即在执行lambda时将其视为实时计时器,但即使是这种情况,我也希望对其进行适当处理。

“延迟触发一次”似乎是一个常见的问题,应该有一种简单的方法来执行此操作,可能是System.Threading中的其他一些库,但我目前所缺少的,但是现在我能想到的唯一解决方案是修改 上面带有间隔运行的专用清理任务。 有什么建议吗?

Chuu asked 2020-08-10T16:26:14Z

12个解决方案

68 votes

我不知道您正在使用哪个版本的C#。 但是我认为您可以通过使用Task库来完成此任务。 然后看起来像这样。

public class PauseAndExecuter

{

public async Task Execute(Action action, int timeoutInMilliseconds)

{

await Task.Delay(timeoutInMilliseconds);

action();

}

}

treze answered 2020-08-10T16:26:20Z

26 votes

.Net 4没有内置功能可以很好地做到这一点。 Thread.Sleep甚至AutoResetEvent.WaitOne(timeout)都不好-它们会占用线程池资源,我已经被烧死了!

最轻巧的解决方案是使用计时器-特别是在您要执行许多任务的情况下。

首先创建一个简单的计划任务类:

class ScheduledTask

{

internal readonly Action Action;

internal System.Timers.Timer Timer;

internal EventHandler TaskComplete;

public ScheduledTask(Action action, int timeoutMs)

{

Action = action;

Timer = new System.Timers.Timer() { Interval = timeoutMs };

Timer.Elapsed += TimerElapsed;

}

private void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e)

{

Timer.Stop();

Timer.Elapsed -= TimerElapsed;

Timer = null;

Action();

TaskComplete(this, EventArgs.Empty);

}

}

然后,再次创建一个调度程序类,非常简单:

class Scheduler

{

private readonly ConcurrentDictionary _scheduledTasks = new ConcurrentDictionary();

public void Execute(Action action, int timeoutMs)

{

var task = new ScheduledTask(action, timeoutMs);

task.TaskComplete += RemoveTask;

_scheduledTasks.TryAdd(action, task);

task.Timer.Start();

}

private void RemoveTask(object sender, EventArgs e)

{

var task = (ScheduledTask) sender;

task.TaskComplete -= RemoveTask;

ScheduledTask deleted;

_scheduledTasks.TryRemove(task.Action, out deleted);

}

}

可以这样称呼它-它非常轻巧:

var scheduler = new Scheduler();

scheduler.Execute(() => MessageBox.Show("hi1"), 1000);

scheduler.Execute(() => MessageBox.Show("hi2"), 2000);

scheduler.Execute(() => MessageBox.Show("hi3"), 3000);

scheduler.Execute(() => MessageBox.Show("hi4"), 4000);

James Harcourt answered 2020-08-10T16:26:57Z

5 votes

我的例子:

void startTimerOnce()

{

Timer tmrOnce = new Timer();

tmrOnce.Tick += tmrOnce_Tick;

tmrOnce.Interval = 2000;

tmrOnce.Start();

}

void tmrOnce_Tick(object sender, EventArgs e)

{

//...

((Timer)sender).Dispose();

}

Nikolay Khilyuk answered 2020-08-10T16:27:17Z

4 votes

我使用此方法将任务计划为特定时间:

public void ScheduleExecute(Action action, DateTime ExecutionTime)

{

Task WaitTask = Task.Delay(ExecutionTime.Subtract(DateTime.Now));

WaitTask.ContinueWith(() => action());

WaitTask.Start();

}

应该注意的是,由于int32最大值,此方法只能工作约24天。

VoteCoffee answered 2020-08-10T16:27:41Z

2 votes

如果您不太关心时间的粒度,则可以创建一个计时器,该计时器每秒滴答一次,并检查需要在ThreadPool上排队的过期Action。 只需使用秒表类检查超时。

您可以使用当前的方法,除了“词典”将“秒表”作为其键并将“操作”作为其值。 然后,您只需对所有KeyValuePair进行迭代,找到到期的秒表,将Action排队,然后将其删除。 但是,您可以从LinkedList获得更好的性能和内存使用率(因为每次都将枚举整个对象,而删除项目则更加容易)。

Garo Yeriazarian answered 2020-08-10T16:28:06Z

2 votes

使用一键式计时器,您拥有的模型绝对是正确的选择。 您当然不想为其中每个创建一个新线程。 您可以有一个线程,并有一个按优先顺序排列的优先操作队列,但这没有必要的复杂性。

尽管我很想尝试一下,但在回调中调用Timeout.Infinite可能不是一个好主意。 我似乎回想起过去做过的事,效果还不错。 我承认,但这是一件很奇怪的事情。

您可以从集合中删除计时器,而不能丢弃它。 如果没有对该对象的引用,则可以进行垃圾回收,这意味着终结器将调用Timeout.Infinite方法。 只是没有您想要的及时。 但这不应该是一个问题。 您只是在短时间泄漏手柄。 只要您没有成千上万的此类物品长期处于闲置状态,就不会有问题。

另一种选择是让一个计时器队列保持分配状态,但是将其禁用(即,其超时和间隔设置为Timeout.Infinite)。 当需要计时器时,可以从队列中拉出一个计时器,进行设置,然后将其添加到您的收藏夹中。 超时到期后,您清除计时器并将其放回队列。 您可以根据需要动态地增加队列,甚至可以不时整理它。

这样可以防止您为每个事件泄漏一个计时器。 相反,您将拥有一个计时器池(非常类似于线程池,不是吗?)。

Jim Mischel answered 2020-08-10T16:28:46Z

1 votes

treze的代码运行正常。 这可能对那些必须使用旧版.NET的用户有所帮助:

private static volatile List _timers = new List();

private static object lockobj = new object();

public static void SetTimeout(Action action, int delayInMilliseconds)

{

System.Threading.Timer timer = null;

var cb = new System.Threading.TimerCallback((state) =>

{

lock (lockobj)

_timers.Remove(timer);

timer.Dispose();

action();

});

lock (lockobj)

_timers.Add(timer = new System.Threading.Timer(cb, null, delayInMilliseconds, System.Threading.Timeout.Infinite));

}

Koray answered 2020-08-10T16:29:06Z

1 votes

文档明确指出System.Timers.Timer具有AutoReset属性,仅针对您的要求:

[HTTPS://MSDN.Microsoft.com/恩-US/library/system.timers.timer.auto reset(V=vs.110).aspx]

Lu4 answered 2020-08-10T16:29:30Z

1 votes

这似乎对我有用。 它允许我在15秒的延迟后调用_connection.Start()。 -1毫秒参数只是说不重复。

// Instance or static holder that won't get garbage collected (thanks chuu)

System.Threading.Timer t;

// Then when you need to delay something

var t = new System.Threading.Timer(o =>

{

_connection.Start();

},

null,

TimeSpan.FromSeconds(15),

TimeSpan.FromMilliseconds(-1));

naskew answered 2020-08-10T16:29:51Z

1 votes

使用Microsoft的Reactive Framework(NuGet“ System.Reactive”),然后可以执行以下操作:

protected void Execute(Action action, int timeout_ms)

{

Scheduler.Default.Schedule(TimeSpan.FromMilliseconds(timeout_ms), action);

}

Enigmativity answered 2020-08-10T16:30:10Z

0 votes

为什么不简单地在异步操作中调用操作参数本身呢?

Action timeoutMethod = () =>

{

Thread.Sleep(timeout_ms);

action();

};

timeoutMethod.BeginInvoke();

Tejs answered 2020-08-10T16:30:35Z

-2 votes

这可能会晚一点,但是这是我当前用于处理延迟执行的解决方案:

public class OneShotTimer

{

private volatile readonly Action _callback;

private OneShotTimer(Action callback, long msTime)

{

_callback = callback;

var timer = new Threading.Timer(TimerProc);

timer.Change(msTime, Threading.Timeout.Infinite);

}

private void TimerProc(object state)

{

try {

// The state object is the Timer object.

((Threading.Timer)state).Dispose();

_callback.Invoke();

} catch (Exception ex) {

// Handle unhandled exceptions

}

}

public static OneShotTimer Start(Action callback, TimeSpan time)

{

return new OneShotTimer(callback, Convert.ToInt64(time.TotalMilliseconds));

}

public static OneShotTimer Start(Action callback, long msTime)

{

return new OneShotTimer(callback, msTime);

}

}

您可以像这样使用它:

OneShotTimer.Start(() => DoStuff(), TimeSpan.FromSeconds(1))

Karsten answered 2020-08-10T16:30:59Z

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值