2 of 4 System.Threading.Timers不在服务器上启动,但在本地运行正常

时间:2017-02-16 07:37:36

标签: c# multithreading timer

我有一个相对简单的控制台应用程序。在Main - 方法中,我有以下代码:

TimeSpan immediately = TimeSpan.Zero;

var userRoutine = new System.Threading.Timer(
    o => SyncUsers(o), null, immediately, userMatchFrequency);
var materialRoutine = new Timer(
    o => SyncMaterials(o), null, immediately, materialSyncFrequency);
var activityRoutine = new Timer(
    o => SyncActivities(o), null, immediately, activitySyncFrequency);
var customerRoutine = new Timer(
    o => SyncCustomers(o), null, immediately, customerSyncFrequency);
while (true)
{
    Console.ReadKey(false);
}

在所有回调方法的开头,我有一个Console.WriteLine(string),在第一行有一条唯一的消息。

然后我在Visual Studio中清理并重建它,然后复制bin-> Release文件夹。当我在本地运行.exe文件时,它工作正常。但是当我的老板在服务器上运行它时,我可以看到它只启动了最后两个计时器。并且它始终如一。

该项目的目标是.NET 4.5.2,这是服务器支持的最新版本,因此我怀疑它与此相关。我能够在其上找到的是,我应该保持对正在运行的线程范围内的所有计时器的引用,以避免垃圾收集。但据我所知,阻止Console.ReadKey() - 在while循环中调用并不能使外部范围"收集"到GC。

我没有经常测试很多命中和错过的理论,所以我可能会放弃计时器并使用我手动管理的Task - 但我'我仍然很好奇这里会发生什么。

2 个答案:

答案 0 :(得分:3)

GC比你想象的要强烈得多。例如。只要构造函数的其余部分不访问任何对象的字段,就可以在构造函数仍在运行时收集对象。变量范围与变量生存期无关。

如果你现在对GC更加怀疑(你应该如此),你可以通过添加一些GC.KeepAlive来验证这个假设:

TimeSpan immediately = TimeSpan.Zero;

var userRoutine = new System.Threading.Timer(
    o => SyncUsers(o), null, immediately, userMatchFrequency);
var materialRoutine = new Timer(
    o => SyncMaterials(o), null, immediately, materialSyncFrequency);
var activityRoutine = new Timer(
    o => SyncActivities(o), null, immediately, activitySyncFrequency);
var customerRoutine = new Timer(
    o => SyncCustomers(o), null, immediately, customerSyncFrequency);
while (true)
{
    Console.ReadKey(false);
}
GC.KeepAlive(userRoutine);
GC.KeepAlive(materialRoutine);
GC.KeepAlive(activityRoutine);
GC.KeepAlive(customerRoutine);

虽然我通常希望在此之后重新构建应用程序,以便以更有机的方式保持引用,并验证它是GC问题。

答案 1 :(得分:3)

  

但据我所知,阻止Console.ReadKey() - 在while循环中调用不会使外部范围“收集”到GC。

事实并非如此。您不以任何方式使用本地变量,因此没有任何东西阻止GC收集它们(虽然在调试模式下或在附加调试器的情况下运行时 - 可能会采取措施来防止这种情况,但不能在没有调试器的发布模式下采取措施)。使用以下代码检查很容易:

static void Main(string[] args) {
    TimeSpan immediately = TimeSpan.Zero;
    var userMatchFrequency = TimeSpan.FromSeconds(1);
    var materialSyncFrequency = TimeSpan.FromSeconds(2);
    var activitySyncFrequency = TimeSpan.FromSeconds(3);
    var customerSyncFrequency = TimeSpan.FromSeconds(4);
    var userRoutine = new System.Threading.Timer(
        o => SyncUsers(o), null, immediately, userMatchFrequency);
    var materialRoutine = new Timer(
        o => SyncMaterials(o), null, immediately, materialSyncFrequency);
    var activityRoutine = new Timer(
        o => SyncActivities(o), null, immediately, activitySyncFrequency);
    var customerRoutine = new Timer(
        o => SyncCustomers(o), null, immediately, customerSyncFrequency);

    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
    GC.WaitForPendingFinalizers();
    Console.ReadKey(false);
}

static void SyncUsers(object o) {
    Console.WriteLine("Sync users");
}

static void SyncMaterials(object o)
{
    Console.WriteLine("Sync meterials");
}

static void SyncActivities(object o)
{
    Console.WriteLine("Sync activities");
}

static void SyncCustomers(object o)
{
    Console.WriteLine("Sync customers");
}

如果您在发布模式下编译它并在没有调试器的情况下运行 - 您根本不会在控制台中看到任何消息,因为所有计时器都会立即被垃圾收集。要解决此问题,请使用GC.KeepAlive

static void Main(string[] args) {
    TimeSpan immediately = TimeSpan.Zero;
    var userMatchFrequency = TimeSpan.FromSeconds(1);
    var materialSyncFrequency = TimeSpan.FromSeconds(2);
    var activitySyncFrequency = TimeSpan.FromSeconds(3);
    var customerSyncFrequency = TimeSpan.FromSeconds(4);
    var userRoutine = new System.Threading.Timer(
        o => SyncUsers(o), null, immediately, userMatchFrequency);
    var materialRoutine = new Timer(
        o => SyncMaterials(o), null, immediately, materialSyncFrequency);
    var activityRoutine = new Timer(
        o => SyncActivities(o), null, immediately, activitySyncFrequency);
    var customerRoutine = new Timer(
        o => SyncCustomers(o), null, immediately, customerSyncFrequency);

    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
    GC.WaitForPendingFinalizers();
    Console.ReadKey(false);

    GC.KeepAlive(userRoutine);
    GC.KeepAlive(materialRoutine);
    GC.KeepAlive(activityRoutine);
    GC.KeepAlive(customerRoutine);
}

或更好 - 在Console.ReadKey之后处置你的计时器(或使用包装):

static void Main(string[] args) {
    TimeSpan immediately = TimeSpan.Zero;
    var userMatchFrequency = TimeSpan.FromSeconds(1);
    var materialSyncFrequency = TimeSpan.FromSeconds(2);
    var activitySyncFrequency = TimeSpan.FromSeconds(3);
    var customerSyncFrequency = TimeSpan.FromSeconds(4);
    using (new Timer(o => SyncUsers(o), null, immediately, userMatchFrequency))
    using (new Timer(o => SyncMaterials(o), null, immediately, materialSyncFrequency))
    using (new Timer(o => SyncActivities(o), null, immediately, activitySyncFrequency))
    using (new Timer(o => SyncCustomers(o), null, immediately, customerSyncFrequency))
         Console.ReadKey(false);                    
}

计时器实现IDisposable,无论如何处理实现它的所有内容都是一个好习惯。这也会阻止他们收集垃圾。