【问题标题】:Will the threadpool queue a timer's callback function, sometimes scheduling more than one thread at the same time?线程池是否会排队一个定时器的回调函数,有时会同时调度多个线程?
【发布时间】:2012-05-26 02:50:39
【问题描述】:

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

问题第 1 部分:

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

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

【问题讨论】:

  • 我认为问题在于 TimerRecalcStatisticsElapsed 方法的运行时间超过 1 秒,因此可能会同时执行多个实例。
  • @joocer 我希望通过将 Autoreset 设置为 false、运行任务并在事件完成后重新启用计时器来避免这种情况。这是一个好的解决方案,还是有更强大的解决方案?

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


【解决方案1】:

来自the documentation on System.Timers.Timer

如果 SynchronizingObject 属性为 null,则 Elapsed 事件为 在 ThreadPool 线程上引发。如果处理 Elapsed 事件 持续时间长于间隔,该事件可能会再次引发另一个 线程池线程。在这种情况下,事件处理程序应该是 可重入。

所以回答你的问题:

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

  2. 假设您没有设置同步对象或以其他方式同步您的回调,是的,多个回调可以重叠。如果你给定时器一个同步对象,它就不会“重叠”事件。

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

【讨论】:

  • 谢谢.. 你知道如何在我的另一个线程上实现 SynchronizingObject 吗?也许我可以使用 John Skeet 的答案,但我不知道如何使用这个实用程序类:stackoverflow.com/a/1841389/328397
  • 我不确定您所说的“在我的另一个线程上实现 SynchronizingObject”是什么意思。您不实现 SynchronizingObject - 它是计时器的一个属性。只要不为空,该计时器就不会与事件重叠。
【解决方案2】:

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

ad 2) 这就是 MSDN 关于Timer 类的说法:

如果 SynchronizingObject 属性为 null,则 Elapsed 事件为 在 ThreadPool 线程上引发。如果处理 Elapsed 事件 持续时间长于间隔,该事件可能会再次引发另一个 线程池线程。在这种情况下,事件处理程序应该是 可重入。

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

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

更新: 我认为 Jon Skeet 的 answer 不能解决您的问题。实现自己的 SynchronizingObject 恕我直言,比必要的更复杂(但如果不知道整个问题就很难说)。我希望这个实现应该可以工作(但我没有测试过):

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; }
    }
}

【讨论】:

  • 谢谢.. 你知道我如何在我的另一个线程上实现 SynchronizingObject 吗?也许我可以使用 John Skeet 的答案,但我不知道如何使用这个实用程序类:stackoverflow.com/a/1841389/328397
猜你喜欢
  • 2011-08-27
  • 1970-01-01
  • 2011-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多