C# 垃圾收集器和计时器停止?
目前我有一个Windows服务,它从UDP连接收集数据并将其放入队列。然后另一个线程获取该数据并将其插入MariaDb数据库。如果插入时间过长且队列过长,则会创建更多使用者线程。当队列再次变小时,这些线程将被破坏。有时,插入在提交时失败,错误为:System.InvalidOperationException:连接必须有效并打开才能提交事务,或者出现其他错误。但是错误会被捕获,插入操作会重试100次。以下是执行插入操作的代码,错误处理在此函数之外:C# 垃圾收集器和计时器停止?,c#,.net,multithreading,mariadb,C#,.net,Multithreading,Mariadb,目前我有一个Windows服务,它从UDP连接收集数据并将其放入队列。然后另一个线程获取该数据并将其插入MariaDb数据库。如果插入时间过长且队列过长,则会创建更多使用者线程。当队列再次变小时,这些线程将被破坏。有时,插入在提交时失败,错误为:System.InvalidOperationException:连接必须有效并打开才能提交事务,或者出现其他错误。但是错误会被捕获,插入操作会重试100次。以下是执行插入操作的代码,错误处理在此函数之外: public void InsertIntoD
public void InsertIntoDB(DataTable dt)
{
Logger.Info(string.Format("Writing {0} rows", dt.Rows.Count), "SQLServerAccess");
string insert = GetInsertString(dt);
using (MySqlConnection conn = Conn)
{
conn.Open();
using (MySqlTransaction transaction = conn.BeginTransaction(IsolationLevel.RepeatableRead))
{
using (MySqlCommand cmd = new MySqlCommand(insert, conn, transaction))
{
foreach (DataColumn column in dt.Columns)
{
MySqlParameter param = GetParameter(column);
cmd.Parameters.Add(param);
}
Logger.Debug("P01: " + System.DateTime.Now.ToString("ss.ffff"), "SQLServerAccess");
cmd.UpdatedRowSource = UpdateRowSource.None;
Logger.Debug("P02: " + System.DateTime.Now.ToString("ss.ffff"), "SQLServerAccess");
using (MySqlDataAdapter mda =
new MySqlDataAdapter()
{
ContinueUpdateOnError = true,
InsertCommand = cmd,
UpdateBatchSize = 10000
})
{
Logger.Debug("P03: " + System.DateTime.Now.ToString("ss.ffff"), "SQLServerAccess");
mda.Update(dt);
Logger.Debug("P04: " + System.DateTime.Now.ToString("ss.ffff"), "SQLServerAccess");
transaction.Commit();
Logger.Debug("P05: " + System.DateTime.Now.ToString("ss.ffff"), "SQLServerAccess");
}
}
}
if (dt.HasErrors)
{
LogErrors(dt);
}
}
Logger.Info(string.Format("Writing {0} rows done", dt.Rows.Count), "SQLServerAccess");
}
这一切通常都很好。但上周我有一种奇怪的行为:
一切正常。一个使用者线程正在运行,处理数据。线程调用insert函数。这需要一段时间,因此会启动第二个使用者线程。第二个线程处理数据并调用insert函数—多个调用都没有问题。然后第一次调用的插入失败,出现上面的错误,第二次调用也失败。所以这两个都将进行重试。同时,接收数据并将其放入队列的线程继续存储队列。线程处理程序每2秒检查一次队列的大小,并在必要时启动一个新线程。只是在这种情况下它不会。计时器似乎不再启动了。你知道为什么会这样吗?我用的是系统计时器
下一个奇怪的事情是:两个运行的使用者线程都多次失败,并出现上面的错误。过了一会儿,错误更改为使用方法“mysql\u native\u password”对用户“xyz”进行主机“xyz.de”的身份验证失败,消息为:连接太多。失败尝试的计数器与数据库的max_连接数完全匹配。这让我得出结论,垃圾收集器停止工作了。因为连接总是在using构造内部打开,并且应该在之后进行处理。这通常是有效的。我在这里用小代码测试了这一点:
class Program2
{
private static MySqlConnection Conn
{
get
{
string connString = "host=" + "xyz.de" + ";" + "port=" + "3305" + ";" + "username=" + "xyz" + ";" + "password=" + "xyz" + ";" + "database=" + "foobar" + ";" + "SSL Mode=" + "Required";
return new MySqlConnection(connString);
}
}
static void Main(string[] args)
{
for (int i = 0; i < 100; i++)
{
try
{
using (MySqlConnection conn = Conn)
{
conn.Open();
using (MySqlTransaction transaction = conn.BeginTransaction(IsolationLevel.RepeatableRead)) {
transaction.Commit();
System.Threading.Thread.Sleep(1000);
throw new Exception();
}
}
}
catch (Exception e)
{
Console.WriteLine("Exception" + e.ToString());
}
}
}
}
因此,我的问题是:
GC会停止工作吗
为什么计时器停止工作了
你对我观察到的行为有什么看法
编辑:
添加线程处理代码。
我现在知道计时器不再启动了,因为它正在等待第二个消费者线程停止
编辑2:
我用一本新词典替换了这本词典
public partial class XYZ
{
private System.Timers.Timer QueueWatchdog = new System.Timers.Timer();
private ConcurrentDictionary<string, ManualResetEvent> stopEvents = new ConcurrentDictionary<string, ManualResetEvent>();
private const byte maxThreadNumber = 6;
private const byte newThreadMultiplier = 50;
private const byte stopThreadHysteresis = 40;
private void defineThreads()
{
QueueWatchdog.Interval = 100; //Will be set higher on the first call.
QueueWatchdog.AutoReset = false; //Will be set true on the first call.
QueueWatchdog.Elapsed += QueueWatchdog_Elapsed;
QueueWatchdog.Start();
THREAD_RX_UDP_DATA = new Thread(UDPListen);
THREAD_RX_UDP_DATA.Priority = ThreadPriority.Normal;
THREAD_RX_UDP_DATA.IsBackground = true;
prepareTcpThread();
}
/// <summary>
/// Gets called by the timer QueueWatchdog. Creates the first Thread for proccessing the <c>insertQueue</c>.
/// Creates an new Thread for processing the <c>insertQueue</c> depending on <c>maxThreadNumber</c> and <c>stopThreadHysteresis</c>.
/// New thread if <c>insertQueue.Count >= THREAD_QUEUE_WORKER.Count* newThreadMultiplier</c>.
/// Destroy thread if <c>insertQueue.Count < ((THREAD_QUEUE_WORKER.Count - 1) * newThreadMultiplier) - stopThreadHysteresis</c>.
/// Up to <c>maxThreadNumber</c> threads will be created including the default thread which only will be destroyed on shutting down the application.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void QueueWatchdog_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (QueueWatchdog.Interval == 100)
{
QueueWatchdog.Stop();
QueueWatchdog.Interval = 2000;
QueueWatchdog.AutoReset = true;
QueueWatchdog.Start();
}
if (THREAD_QUEUE_WORKER.Count == 0)
{
Logger.Info("Starting base QueueWorker", "QueueWatchdog_Elapsed");
Thread thread = new Thread(QueueWorker);
thread.Priority = ThreadPriority.Normal;
thread.IsBackground = true;
thread.Name = "0";
THREAD_QUEUE_WORKER.Add(thread);
stopEvents.TryAdd(thread.Name, new ManualResetEvent(false));
}
else if (insertQueue.Count < ((THREAD_QUEUE_WORKER.Count - 1) * newThreadMultiplier) - stopThreadHysteresis)
{
int index = THREAD_QUEUE_WORKER.Count - 1;
Logger.Info(string.Format("Stopping QueueWorker {0}", index), "QueueWatchdog_Elapsed");
Thread thread = THREAD_QUEUE_WORKER[index];
ManualResetEvent stopEvent;
if (!stopEvents.TryGetValue(thread.Name, out stopEvent))
{
return;
}
stopEvent.Set();
QueueWatchdog.Stop();
Logger.Info(string.Format("Waiting for QueueWorker {0} to end.", index), "QueueWatchdog_Elapsed");
thread.Join();
THREAD_QUEUE_WORKER.RemoveAt(index);
stopEvents.TryRemove(thread.Name, out _);
QueueWatchdog.Start();
}
else if (insertQueue.Count >= THREAD_QUEUE_WORKER.Count * newThreadMultiplier)
{
if (THREAD_QUEUE_WORKER.Count < maxThreadNumber)
{
int index = THREAD_QUEUE_WORKER.Count;
Logger.Info(string.Format("Starting QueueWorker {0}", index), "QueueWatchdog_Elapsed");
Thread thread = new Thread(QueueWorker);
thread.Priority = ThreadPriority.Normal;
thread.IsBackground = true;
thread.Name = index.ToString();
thread.Start();
THREAD_QUEUE_WORKER.Add(thread);
stopEvents.TryAdd(thread.Name, new ManualResetEvent(false));
}
}
}
}
你确定你的康涅狄格州财产就是这样写的吗?你能仔细检查一下你没有重复使用呼叫之间的连接吗?请分享一个。这让我得出结论,垃圾收集器停止工作了。如果您正确使用,垃圾收集器实际上与处理这些连接无关。我要说的是,停止GC的情况极不可能发生。@mjwills说实话,我认为在排队逻辑上没有任何值得节省的地方。所有这些代码都可以替换为一个简单的信号量和ThreadPool/Task.Run。我可能会重写以包括async/await。最后,您将使用async/await内联数据库逻辑。