混合线程定时器和PLINQ



我在线程计时器回调中调用PLINQ的ForAll扩展时遇到了问题。这将无限地创建线程。代码示例是实际问题的简单简化版本。

class Program
{
    static List<int> x = new List<int>();
    static void Main(string[] args)
    {                   
        x = Enumerable.Range(0, 9).ToList();
        System.Threading.Timer[] timers = new System.Threading.Timer[10];
        for (int i = 0; i < 10; i++)
            timers[i] = new System.Threading.Timer(ElapsedCallback, null, 1000, 1000);
        Console.ReadLine();
    }
    static void ElapsedCallback(object state)
    {
        int id = Thread.CurrentThread.ManagedThreadId;
        x.AsParallel().ForAll(y => Console.WriteLine(y + " - " + Thread.CurrentThread.ManagedThreadId + " - " + id));
    }
}

如果在任务管理器中监视,可以看到线程数将上升,直到进程挂起。如果我限制线程池的大小,进程将创建线程直到这个大小,然后也卡住了。

如果在其他代码中也看到这种模式。例如Firebird ADO中的ConnectionPool。Net Provider这样做也是为了清理未使用的连接。如果我在这里做一些愚蠢的事情,我不是唯一一个;)见解吗?

编辑:Jim问了一些背景,所以…

此模式用于只读事务的事务池。应用程序可能在十几个不同的数据库中打开了多个只读事务。每个数据库都有自己的事务池(Pool of Transaction)和自己的Timer,后者定期提交和处置池中该数据库的旧事务。然后通过PLINQ的ForAll并行处理提交的每个事务。

您将创建10个计时器,每个计时器将每秒滴答一次。这些计时器需要超过一秒的时间来输出所有的数据。因此,您将为每个计时器获得下一个标记,并且将创建更多在下一个计时器之前不会完成的线程,并且…是啊,那是永远不会完成的。

即使只使用一个计时器,回调也有可能在下一个计时器之前完成,也就是一秒钟之后。如果您使用单个计时器,您可以使用以下几种方法之一来解决这个问题:

  1. 回呼时禁用定时器,退出时重新启用定时器。这将防止(在大多数情况下)在处理回调时发生滴答声。然而,在某些情况下,多个节拍仍然可能发生,最常见的情况是系统负载过重。
  2. 创建一个锁对象,并在进入回调时使用Monitor.TryEnter尝试获取锁。如果无法获得锁,则退出。当然,如果你确实获得了锁,那么你会想在退出回调之前调用Monitor.Exit
  3. 将计时器设置为一次性,以便它只触发一次。当回调完成它的工作时,回调将重新初始化计时器,同样是一次性的。这工作得很好,尽管你的回调是在最后一个回调结束后一秒钟执行的,而不是每秒一次。

如果没有更多关于你真正想做什么的信息,就不可能给出更具体的建议。

最新更新