线程池是否会对计时器的回调函数进行排队,有时同时调度多个线程
本文关键字:线程 排队 调度 是否 计时器 回调 函数 | 更新日期: 2023-09-27 18:34:46
在下面的代码中,TimerRecalcStatisticsElapsed
应该只有一个运行实例。 此回调调用的工作器方法按顺序运行,一次最多运行一个线程。
问题部分 1:
如果计时器的回调运行线程池线程(而不是在单独的线程上运行回调(,那么说线程池可能会根据条件(达到最大线程数、线程池内部逻辑(对线程进行排队和延迟以供以后执行是否正确?
问题部分 2:
假设一个计时器回调可以排队等待除立即执行以外的任何内容,这是否意味着任意数量的线程回调可以并发执行?
问题第 3 部分
假设第 2 部分为真,这是否意味着下面的代码可以同时运行多个回调?
我问的原因是,在多 CPU 服务器上运行着这个类的数千个实例。 我还看到数据损坏与// Do Work Here
的无序操作一致。
旁白
// Do work here
内部使用 System.Collections.Dictionary 并编辑 y 的值。 它还会删除串行调用的后续函数的一些键。 该函数缺少以前在第一次调用中存在的键 (x(。 我认为这是因为最终语句存在竞争条件obj.cleanupdata()
public class SystemTimerTest
{
readonly System.Timers.Timer timerRecalcStatistics;
readonly System.Diagnostics.Stopwatch stopwatchForRecalcStatistics = new System.Diagnostics.Stopwatch();
public SystemTimerTest(TimeSpan range, DataOverwriteAction action)
{
int recalculateStatisticsEveryXMillseconds = 1000;
timerRecalcStatistics = new System.Timers.Timer(recalculateStatisticsEveryXMillseconds);
timerRecalcStatistics.AutoReset = true;
timerRecalcStatistics.Elapsed += new System.Timers.ElapsedEventHandler(TimerRecalcStatisticsElapsed);
timerRecalcStatistics.Interval = recalculateStatisticsEveryXMillseconds;
timerRecalcStatistics.Enabled = true;
this.maxRange = range;
this.hashRunningTotalDB = new HashRunningTotalDB(action);
this.hashesByDate = new HashesByDate(action);
this.dataOverwriteAction = action;
}
private void TimerRecalcStatisticsElapsed(object source, System.Timers.ElapsedEventArgs e)
{
stopwatchForRecalcStatistics.Start();
Console.WriteLine("The TimerRecalcStatisticsElapsed event was raised at {0}", e.SignalTime.ToString("o"));
// DO WORK HERE
stopwatchForRecalcStatistics.Stop();
double timeBuffer = GetInterval(IntervalTypeEnum.NearestSecond, e.SignalTime) - stopwatchForRecalcStatistics.ElapsedMilliseconds;
if (timeBuffer > 0)
timerRecalcStatistics.Interval = timeBuffer;
else
timerRecalcStatistics.Interval = 1;
stopwatchForRecalcStatistics.Reset();
timerRecalcStatistics.Enabled = true;
}
}
ad 1( ThreadPool 是否可以延迟回调方法的执行并不重要,因为无论如何回调不能保证在另一个计时器间隔过去之前完成执行(例如,线程调度程序可以挂起线程,或者回调可能会调用长时间运行的函数(。
广告2(以下是 MSDN 对计时器类的描述:
如果 同步对象 属性为 null,则 已用 事件为 在线程池线程上引发。如果处理已用事件 持续时间超过间隔,事件可能会在另一个事件上再次引发 线程池线程。在这种情况下,事件处理程序应为 折返。
所以答案是肯定的,回调可以同时在多个线程上执行。
广告3(是的。并且您应该避免在回调方法中使用共享资源(timerRecalcStatistics,stopwatchForRecalcStatistics(,或者同步对这些共享资源的访问(例如使用lock(,或者将适当的对象设置为计时器的SynchronizingObject属性,或将Timer 的AutoReset属性设置为false(并在计时器回调结束时再次启用计时器(。
更新:我认为Jon Skeet的回答并不能解决你的问题。恕我直言,实现自己的 SynchonizingObject 也比必要的复杂得多(但如果不了解整个问题,很难说(。我希望这个实现应该有效(但我没有测试它(:
public class MySynchronizeInvoke : ISynchronizeInvoke
{
private object SyncObject = new Object();
private delegate object InvokeDelegate(Delegate method, object[] args);
public IAsyncResult BeginInvoke(Delegate method, object[] args)
{
ElapsedEventHandler handler = (ElapsedEventHandler)method;
InvokeDelegate D = Invoke;
return D.BeginInvoke(handler, args, CallbackMethod, null);
}
private void CallbackMethod(IAsyncResult ar)
{
AsyncResult result = ar as AsyncResult;
if(result != null)
((InvokeDelegate)result.AsyncDelegate).EndInvoke(ar);
}
public object EndInvoke(IAsyncResult result)
{
result.AsyncWaitHandle.WaitOne();
return null;
}
public object Invoke(Delegate method, object[] args)
{
lock(SyncObject)
{
ElapsedEventHandler handler = (ElapsedEventHandler)method;
handler(args[0], (ElapsedEventArgs)args[1]);
return null;
}
}
public bool InvokeRequired
{
get { return true; }
}
}
来自 System.Timers.Timer 的文档:
如果 同步对象 属性为 null,则 已用 事件为 在线程池线程上引发。如果处理已用事件 持续时间超过间隔,事件可能会在另一个事件上再次引发 线程池线程。在这种情况下,事件处理程序应为 折返。
所以要回答你的问题:
-
是的,它在线程池线程上运行,并且像其他任何东西一样受到线程池填满和延迟的影响。 鉴于线程池现在最多有数百个线程,这应该不是问题。 如果是,你就有更大的问题。
-
假设您没有设置同步对象或以其他方式同步回调,是的,多个回调可以重叠。 如果为计时器指定一个同步对象,它将不会"重叠"事件。
-
您提供的代码不会以任何方式同步其回调,因此是的,它可以有多个重叠,同时执行回调的副本。 如果希望类的所有实例彼此同步,则应使用类似于 lock 语句的内容来同步该方法;如果希望类的每个实例在任何给定时间只运行一个回调,则应使用计时器的 SynchronizingObject。