C# 垃圾收集器和计时器停止?

C# 垃圾收集器和计时器停止?,c#,.net,multithreading,mariadb,C#,.net,Multithreading,Mariadb,目前我有一个Windows服务,它从UDP连接收集数据并将其放入队列。然后另一个线程获取该数据并将其插入MariaDb数据库。如果插入时间过长且队列过长,则会创建更多使用者线程。当队列再次变小时,这些线程将被破坏。有时,插入在提交时失败,错误为:System.InvalidOperationException:连接必须有效并打开才能提交事务,或者出现其他错误。但是错误会被捕获,插入操作会重试100次。以下是执行插入操作的代码,错误处理在此函数之外: public void InsertIntoD

目前我有一个Windows服务,它从UDP连接收集数据并将其放入队列。然后另一个线程获取该数据并将其插入MariaDb数据库。如果插入时间过长且队列过长,则会创建更多使用者线程。当队列再次变小时,这些线程将被破坏。有时,插入在提交时失败,错误为:System.InvalidOperationException:连接必须有效并打开才能提交事务,或者出现其他错误。但是错误会被捕获,插入操作会重试100次。以下是执行插入操作的代码,错误处理在此函数之外:

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内联数据库逻辑。