【问题标题】:Using Tasks to perform delayed "timer-style" operations使用任务执行延迟的“计时器式”操作
【发布时间】:2016-06-09 22:27:03
【问题描述】:

我的应用程序中有一个事件,它改变了对象的状态并引用该状态以在 10 秒后重置。但如果在这 10 秒内再次触发该事件,则该事件不会更改状态,而只是将计时器重置为零。

为了简明易懂,这是我的代码的作用

  1. 我有一个安排任务在 10 秒后运行的事件
  2. 如果某个类型的一个任务被调度,并且另一个相同的请求进来,则现有的未来任务的“执行时间”会被调整。

目前我正在使用在线程中操作的“ConcurrentQueue”进行此操作

public class ToDo
{
 public DateTime Expires { get; set; }
 public Action StuffToDo { get; set; }
}

然后是我的线程

public void MyWorkerThread(object parameters)
{
 while (!<check if cancellation requested>)
 {
   ToDo stuff;
   if (myConcurrentQueue.TryDequeue(out stuff))
   {
     if (DateTime.Now > stuff.Expires)
      {
        Task.StuffToDo.Invoke();
      }
     else 
      {
       // queue it back if it is not time to execute it
       //
        myConcurrentQueue.Enqueue(stuff);
      }
   }
 }
}

这是我将任务排队的事件处理程序

private ConcurrentDictionary<ToDo> _todoDictionary = new ConcurrentDictionary<ToDo>();
private ConcurrentQueue<ToDo> myConcurrentQueue = new ConcurrentQueue<ToDo>();
public void MyEventFired(MyEventArgs e)
{
 ToDo todo = null;

 if (_todoDictionary.ContainsKey(e.TaskType)) 
 {
   _todoDictionary.TryRemove(out todo);
   if (DateTime.Now >= task.Expires) 
   {
     todo = new ToDo();
     todo.StuffToDo = new Action(() => { /* stuff here */ });
   }   
 } 
 else
 {
   todo = new ToDo();
   todo.StuffToDo = new Action(() => { /* stuff here */ });
 }

 todo.Expires = DateTime.Now + TimeSpan.FromSeconds(10.0);

 _todoDictionary.Add(e.TaskType, todo);
 myConcurrentQueue.Enqueue(todo);
}

有人向我暗示,我可以使用 TPL 完成上述所有操作,而且我不需要工作线程,我可以使用“Task.Delay”。我仍在考虑如何处理它。任何想法将不胜感激。

这是我想做的事情 我希望根据 Task.Run 和/或 Task.Delay().ContinueWith 序列重写工作线程。

【问题讨论】:

  • 发布您想要做的事情,而不是您尝试的方式。反应式扩展已经有计时器、窗口等。TPL 数据流已经有一个由委托处理的消息队列,可能包含延迟。或者你可以写for(;;){...; await someMethod(someData); await Task.Delay(10000);}
  • @PanagiotisKanavos 我想使用 TPL 重写代码。如果我的问题不够明显,请道歉。我会编辑。
  • 但是什么你想要实现?每 10 秒只处理一条消息?消息之间延迟 10 秒?一次处理在 10 秒窗口内收到的所有消息?在 10 秒的窗口内只处理 一个 消息?
  • 例如,Rx 的Throttle 可以丢弃传入的事件,并且在 10 秒的窗口中只处理其中一个。实际语法比文档简单得多
  • @PanagiotisKanavos 我再次道歉。我显然是一个糟糕的问题作者。我编辑了我的问题并添加了我的代码的作用。感谢您的耐心等待。

标签: c# multithreading


【解决方案1】:

您当前的MyWorkerThread 代码正忙于旋转循环。它在 CPU 内核上驱动到 100%。您应该会听到 CPU 风扇因此而旋转的声音。

以下是我如何实现可重置延迟:

volatile int targetTicks = Environment.TickCount + (10 * 1000);

async Task WaitUntilTargetReachedAsync() {
 while (true) {
  var currentTicks = Environment.TickCount; //stabilize value
  var targetTicksLocal = targetTicks; //volatile read, stabilize value
  if (currentTicks >= targetTicksLocal) break; //Target reached.
  await Task.Delay(TimeSpan.FromMilliseconds(targetTicksLocal - currentTicks));
 }
}

WaitUntilTimeoutAsync() 将在达到targetTicks 时完成。您可以随时向targetTicks 添加时间,WaitUntilTimeoutAsync() 将进行调整。这样您就可以将计时器重置为现在加上 10 秒:

targetTicks = Environment.TickCount + (10 * 1000);

既然你有一个任务可以在你需要的时候完成,你可以根据它来采取行动:

targetTicks = Environment.TickCount + (10 * 1000); //Initial timer configuration.
await WaitUntilTimeoutAsync();
InvokeMyAction();

【讨论】:

  • 不,我忽略了。您可以为此使用锁。您也可以将其标记为volatile,这就足够了。但这很难理解并得出正确的结论。
猜你喜欢
  • 2015-09-14
  • 1970-01-01
  • 2013-11-11
  • 2013-02-22
  • 1970-01-01
  • 2011-05-18
  • 1970-01-01
  • 1970-01-01
  • 2020-06-01
相关资源
最近更新 更多