/// <summary>
        /// Gets a client from the pool if one is available, otherwise creates one.
        /// </summary>
        /// <remarks>If capacity is reached, an exception is raised.</remarks>
        public ScadaClient GetClient(ConnectionOptions connectionOptions)
        {
            if (connectionOptions == null)
            {
                throw new ArgumentNullException(nameof(connectionOptions));
            }

            string      optionsKey = GetOptionsKey(connectionOptions);
            ClientGroup clientGroup;

            lock (clientGroups)
            {
                if (!clientGroups.TryGetValue(optionsKey, out clientGroup))
                {
                    clientGroup = new ClientGroup(connectionOptions, Capacity, ClientMode);
                    clientGroups.Add(optionsKey, clientGroup);
                }
            }

            DateTime    utcNow      = DateTime.UtcNow;
            ScadaClient scadaClient = clientGroup.GetClient(utcNow);

            // cleanup the pool
            if (utcNow - lastCleanupTime > CleanupPeriod)
            {
                RemoveOutdatedClients(utcNow);
            }

            return(scadaClient);
        }
            /// <summary>
            /// Removes and returns the client at the end of the client list.
            /// </summary>
            private ScadaClient PopClient()
            {
                int         lastIndex   = availableClients.Count - 1;
                ScadaClient scadaClient = availableClients[lastIndex];

                availableClients.RemoveAt(lastIndex);
                return(scadaClient);
            }
            /// <summary>
            /// Returns the client to the group.
            /// </summary>
            public void ReturnClient(ScadaClient client, DateTime nowDT)
            {
                lock (this)
                {
                    LastAccessTime = nowDT;

                    if (usedClients.Remove(client.ClientID))
                    {
                        availableClients.Add(client);
                    }
                }
            }
            /// <summary>
            /// Gets a client from the group if one is available, otherwise creates one.
            /// </summary>
            public ScadaClient GetClient(DateTime nowDT)
            {
                lock (this)
                {
                    LastAccessTime = nowDT;
                    ScadaClient scadaClient = availableClients.Count > 0
                        ? PopClient()
                        : CreateClient();

                    usedClients[scadaClient.ClientID] = scadaClient;
                    return(scadaClient);
                }
            }
        /// <summary>
        /// Returns the client to the pool.
        /// </summary>
        public void ReturnClient(ScadaClient client)
        {
            if (client != null)
            {
                ClientGroup clientGroup;

                lock (clientGroups)
                {
                    clientGroups.TryGetValue(GetOptionsKey(client.ConnectionOptions), out clientGroup);
                }

                clientGroup?.ReturnClient(client, DateTime.UtcNow);
            }
        }