我有一个相对简单的控制台应用程序。在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
- 但我'我仍然很好奇这里会发生什么。
答案 0 :(得分:3)
如果你现在对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
,无论如何处理实现它的所有内容都是一个好习惯。这也会阻止他们收集垃圾。