在C#中创建"运行一次"时间延迟函数的最佳方法

Chu*_*huu 42 c# timer

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

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

long currentKey = 0;
ConcurrentDictionary<long, Timer> timers = new ConcurrentDictionary<long, Timer>();

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);
}
Run Code Online (Sandbox Code Playgroud)

问题是从回调本身调用Dispose()不是很好.我不确定"脱落"结束是否安全,即计时器在他们的lambdas执行时被认为是活的,但即使是这种情况,我宁愿妥善处理它.

"一次延迟起火"似乎是一个常见的问题,应该有一个简单的方法来做到这一点,可能是System.Threading中的其他一些库我想念,但是现在我能想到的唯一解决方案是修改上面有一个间隔运行的专用清理任务.有什么建议?

tre*_*eze 68

我不知道您使用的是哪个版本的C#.但我认为你可以通过使用任务库来实现这一目标.那么它看起来就像那样.

public class PauseAndExecuter
{
    public async Task Execute(Action action, int timeoutInMilliseconds)
    {
        await Task.Delay(timeoutInMilliseconds);
        action();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 实际上,您可以使用 ```Task.Delay(timeoutInMilliseconds).ContinueWith(t =&gt; yourAction());``` 使其变得更简单 (3认同)

Jam*_*urt 26

.Net 4没有任何内置功能可以很好地完成这项任务.Thread.Sleep甚至AutoResetEvent.WaitOne(超时)都不好 - 它们会占用线程池资源,我已经烧了试试这个!

最轻的解决方案是使用计时器 - 特别是如果你有许多任务要扔掉它.

首先制作一个简单的预定任务类:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

class Scheduler
{        
    private readonly ConcurrentDictionary<Action, ScheduledTask> _scheduledTasks = new ConcurrentDictionary<Action, ScheduledTask>();

    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);
    }
}
Run Code Online (Sandbox Code Playgroud)

它可以调用如下 - 并且非常轻量级:

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);
Run Code Online (Sandbox Code Playgroud)


Vot*_*fee 5

我使用这种方法来安排特定时间的任务:

public void ScheduleExecute(Action action, DateTime ExecutionTime)
{
    Task WaitTask = Task.Delay(ExecutionTime.Subtract(DateTime.Now));
    WaitTask.ContinueWith(() => action());
    WaitTask.Start();
}
Run Code Online (Sandbox Code Playgroud)

应该注意的是,由于 int32 最大值,这仅适用于大约 24 天。

  • 不要将毫秒传递给 `Task.Delay()`,使用从 `Subtract()` 方法返回的 `TimeSpan`。 (4认同)

Myk*_*iuk 5

我的例子:

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();
}
Run Code Online (Sandbox Code Playgroud)