简体   繁体   中英

2 of 4 System.Threading.Timers don't start on the server, but runs fine locally

I have a relatively simple console application. In the Main -method I have the following code:

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

And in the start of all the callback methods I have a Console.WriteLine(string) with a unique message on the very first line.

I've then cleaned and rebuilt it in Visual Studio, then copied the bin->Release folder. When I run the .exe file locally, it works fine. But when my boss runs it on the server, I can see that it only starts the last two Timers. And it does so consistently.

The project is targeted .NET 4.5.2 and that's the latest the server supports, so I doubt it's related to that. All I've been able to find on it, is that I should keep a reference to all timers in the scope of the running thread, to avoid garbage collection. But to the best of my knowledge, the blocking Console.ReadKey() -call in a while loop doesn't make the outside scope "collectible" to the GC.

I don't have the luxury of testing a lot of hit and miss theories, so I'm probably going abandon Timers and use Task s that I manage manually - but I'm still curious what could be going on here.

GC is far more aggressive than you imagine. Eg an object can be collected whilst its constructor is still running, provided that the remaining part of the constructor doesn't access any of the object's fields. Variable scope is not related to variable lifetime .

If you now are more suspicious of GC (and you should be), you can verify this supposition by adding some GC.KeepAlive s:

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

Though I'd generally look to restructure the application after this point so that the references are kept alive in a more organic fashion, having verified that it is a GC issue.

But to the best of my knowledge, the blocking Console.ReadKey()-call in a while loop doesn't make the outside scope "collectible" to the GC.

That is not true. You don't use your local variables in any way, so nothing prevents GC from collecting them (though in Debug mode or when you run with debugger attached - measures might be taken to prevent that, but not in Release mode without debugger). It's easy to check with the following code:

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

If you compile this in Release mode and run without debugger - you won't see any messages in console at all, because all your timers will be immediately garbage collected. To solve this problem - use 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);
}

Or better - dispose your timers after Console.ReadKey (or wrap in using):

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

Timers implement IDisposable and it's a good practice to dispose everything that implements it anyway. This will also prevent their garbage collection.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM