簡體   English   中英

2/4 System.Threading.Timers不在服務器上啟動,但在本地運行良好

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

我有一個相對簡單的控制台應用程序。 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,這是服務器支持的最新版本,因此我懷疑它是否與此有關。 我所能找到的就是,我應該在運行線程范圍內保留對所有計時器的引用,以避免垃圾回收。 但是據我所知,在while循環中阻塞Console.ReadKey()調用不會使外部范圍對GC來說是“可收集的”。

我沒有足夠的能力來測試很多命中和失敗的理論,所以我可能會放棄使用Timers並使用我手動管理的Task ,但是我仍然很好奇這里可能發生的事情。

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問題。

但據我所知,while循環中阻塞Console.ReadKey()的調用不會使外部范圍對GC來說是“可收集的”。

那是不對的。 您不會以任何方式使用局部變量,因此不會阻止GC收集它們(盡管在Debug模式下或在連接調試器的情況下運行-可能會采取措施防止這種情況的發生,但如果沒有調試器,則不會在Release模式下使用)。 使用以下代碼很容易檢查:

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 ,因此最好處置掉所有實現它的東西。 這也將阻止其垃圾回收。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM