线程池是否会对计时器的回调函数进行排队,有时会同时调度多个线程?

时间:2012-05-18 14:07:58

标签: c# multithreading collections task-parallel-library interlocked

在下面的代码TimerRecalcStatisticsElapsed中应该只运行一个实例。此回调调用的worker方法按顺序运行with a maximum of one thread running at a time

问题第1部分:

如果定时器的回调运行一个线程池线程(as opposed to running the callback on a separate thread),那么说线程池可能会排队并推迟线程以便以后根据条件执行(MaxThreads到达,线程池内部逻辑)吗? / p>

问题第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;
    }
 }

2 个答案:

答案 0 :(得分:4)

ad 1)ThreadPool是否可以推迟回调方法的执行并不重要,因为无论如何回调都不能保证在另一个定时器间隔过去之前完成执行(例如,线程调度程序可以挂起线程,或者回调可能呼叫长时间运行的功能)。

ad 2)这就是MSDN对Timer类所说的内容:

  

如果SynchronizingObject属性为null,则Elapsed事件为   在ThreadPool线程上引发。如果处理了Elapsed事件   持续时间超过Interval,事件可能会再次提升   ThreadPool线程。在这种情况下,事件处理程序应该是   折返。

所以答案是肯定的,回调可以同时在多个线程上执行。

ad 3)是的。您应该避免在回调方法中使用共享资源(timerRecalcStatistics,stopwatchForRecalcStatistics),或者同步访问这些共享资源(例如使用lock),或者将相应的对象设置为Timer的SynchronizingObject属性,或者设置Timer的AutoReset属性为false(并在计时器回调结束时再次启用计时器)。

<强>更新: Jon Skeet的answer并没有解决你的问题。同时实现你自己的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; }
    }
}

答案 1 :(得分:3)

来自the documentation on System.Timers.Timer

  

如果SynchronizingObject属性为null,则Elapsed事件为   在ThreadPool线程上引发。如果处理了Elapsed事件   持续时间超过Interval,事件可能会再次提升   ThreadPool线程。在这种情况下,事件处理程序应该是   折返。

所以回答你的问题:

  1. 是的,它在线程池线程上运行,并且受到线程池的填充和延迟,就像其他任何东西一样。鉴于线程池现在最多有数百个线程,这应该不是问题。如果是,你就会遇到更大的问题。

  2. 假设您没有设置同步对象或以其他方式同步回调,是的,多个回调可能会重叠。如果您为计时器提供同步对象,它将不会“重叠”事件。

  3. 您提供的代码不会以任何方式同步它的回调,因此它可以有多个重叠,同时执行回调的副本。如果希望类的所有实例彼此同步,则应使用类似锁定语句的方法同步方法,或者如果希望类的每个单独实例只运行一个回调,则应使用计时器的SynchronizingObject在任何给定的时间。