如何解决从Visual Studio启动后,C#控制台应用程序可以正常工作,发布后仅运行3个周期,这是正常现象吗?
我制作了一个C#控制台应用程序(.NET CORE 5.0),每分钟检查一次MySQL数据库中的更改,并发送包含更改的电子邮件。
如果我直接从Visual Studio 2019运行此应用程序,它将正常工作而不会出现任何问题。
如果我在发布后运行它,它将仅执行3个周期,并且控制台窗口保持打开状态。没有错误或其他任何东西。
第一个屏幕截图来自通过Visual Studio 2019运行
此屏幕截图来自发布后直接从桌面运行
static void Main(string[] args)
{
TimerCallback callback = new TimerCallback(DoStuff);
Timer stateTimer = new Timer(callback,null,1000);
for (; ; )
{
Thread.Sleep(100);
}
}
static public void DoStuff(Object stateInfo)
{
DataTable DtblEmployee = DatabaseClass.GetEmployeeList();
foreach (DataRow row in DtblEmployee.Rows)
{
foreach (var item in row.ItemArray)
{
string Str = RandomStringGenerator.GetRandomAlphanumericString(8);
DatabaseClass.EmployeeUpdate(item.ToString(),Str);
EmailClass.SendEmail(item.ToString(),Str);
}
}
Console.WriteLine("Last check was @ {0}",DateTime.Now.ToString("h:mm:ss"));
Console.WriteLine("{0} e-mail(s) were send.",DtblEmployee.Rows.Count);
}
该程序应该永远运行。
很高兴收到您的来信,如果需要更多信息,请询问。
编辑:添加了额外的代码以显示数据库
public static void EmployeeUpdate(string Emailadress,string Password)
{
string connectionstring;
connectionstring = "server=1.1.1.1;user id=User;password=Pass;port=3306;persistsecurityinfo=True;database=Test";
connection = new MySqlConnection(connectionstring);
try
{
connection.Open();
var cmd = new MySqlCommand("UPDATE Users SET Password=@param_val_1,GeneratePassword=@param_val_3 where Username=@param_val_2",connection);
cmd.Parameters.AddWithValue("@param_val_1",Password);
cmd.Parameters.AddWithValue("@param_val_2",Emailadress);
cmd.Parameters.AddWithValue("@param_val_3",0);
cmd.ExecuteScalar();
cmd.Dispose();
}
catch (MySqlException ex)
{
switch (ex.Number)
{
case 0:
Console.WriteLine("Cannot connect to server. Contact administrator");
break;
case 1045:
Console.WriteLine("Invalid username/password,please try again");
break;
}
}
finally
{
connection.Close();
}
}
解决方法
我强烈希望问题在于您的Timer
正在被垃圾回收并最终确定,并且这阻止了回调的执行。
当您在调试器中的Visual Studio 中运行代码时,JIT在垃圾回收方面不那么积极,这就是为什么它在这种情况下可以正常工作的原因。
要解决的最小更改是在Main
方法的末尾添加以下行:
GC.KeepAlive(stateTimer);
或者,根据quetzalcoatl's answer,您可以为计时器使用using
语句。这两种方法都将具有使计时器在方法执行期间保持活动状态的预期效果。
我认为您应该探索的另一种方法是根本不使用计时器,而只是在Main
方法内循环并直接调用DoStuff
方法。您仍将在该循环中调用Sleep
,该循环将处理计时方面。显然,这将影响代码运行的准确时间,但最终可能会变得更易于理解和调试。
此外,我建议您对异常处理的使用要多一些。确定是否要让代码在任何一次迭代引发异常时停止循环,并在代码中明确显示该异常。
,我100%同意JonSkeet的原因。
是GC清除stateTimer变量。在此行之后,此变量不再使用,编译器可以从堆栈中释放它,然后GC可以释放计时器。
在不同环境中运行应用程序时,GC可能会使用不同的设置规则。调试会话,控制台应用程序,IIS应用程序,用于SqlServer的模块等-它们都具有关于何时以及如何积极运行GC的不同规则。在调试会话下,它还可以清理此变量,但也可以在数小时或数天后执行此操作,也许可以给您更多的时间检查事物?在自由运行的控制台应用程序下,它发生得更快。
GC还有其硬性规则,必须始终遵守这些硬性规则:如果使用了变量,则不能清除它。
JonSkeet建议固定stateTimer,我不同意。这是最后的选择。
更好,只需使用USING指令,因为Timer是IDisposable:
TimerCallback callback = new TimerCallback(DoStuff);
using(Timer stateTimer = new Timer(callback,null,1000))
for (; ; )
{
Thread.Sleep(100);
}
该变量仍未使用,您甚至可以删除它并写入
using(new Timer(callback,1000))
但即使现在,using()
语句仍会记住Timer对象,并防止GC太早清理它。 (必须记住该对象可以在循环结束时调用Dispose()。)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。