Home > Net >  Queued tasks in thread pool are exiting, not being released back into pool
Queued tasks in thread pool are exiting, not being released back into pool

Time:08-31

I'm writing a C# Blazor MAUI app and am trying to perform some automated background tasks. Every 30 seconds, my applications scans the configuration to perform a connection test on a set of user-defined servers.

public static bool CanConnect(string host, int port, TimeSpan timeout) {
    try {
        using (var client = new TcpClient()) {
            var result = client.BeginConnect(host, port, null, null);
            var success = result.AsyncWaitHandle.WaitOne(timeout);
            client.EndConnect(result);
            System.Diagnostics.Debug.Print($"Success! {host}:{port}");
            return success;
        }
    } catch {
        System.Diagnostics.Debug.Print($"Failed connecting to {host}:{port}");
        return false;
    }
}

When my scheduler begins queuing up these tasks, I notice a very slight (but noticeable) hiccup on the GUI.

clusters.AsParallel()
        .ForAll(item => item.Status = ClusterIsAccessible(item) ? Cluster.ConnectionStatus.Online : Cluster.ConnectionStatus.Offline);

I believe the hiccup is a result of threads being created. I am noticing that when the jobs complete, the threads that are used to scan the connections will exit / timeout after 20-25s.

The thread 0x225c has exited with code 0 (0x0).
The thread 0x4d2c has exited with code 0 (0x0).
The thread 0x7c28 has exited with code 0 (0x0).
The thread 0x6724 has exited with code 0 (0x0).
The thread 0x822c has exited with code 0 (0x0).
The thread 0x849c has exited with code 0 (0x0).
The thread 0x5a24 has exited with code 0 (0x0).
The thread 0x86ac has exited with code 0 (0x0).
The thread 0x8840 has exited with code 0 (0x0).
The thread 0x22f8 has exited with code 0 (0x0).
The thread 0x74e0 has exited with code 0 (0x0).
The thread 0x7550 has exited with code 0 (0x0).
The thread 0x8b80 has exited with code 0 (0x0).
The thread 0x4d48 has exited with code 0 (0x0).
The thread 0x14a8 has exited with code 0 (0x0).
The thread 0x5ed0 has exited with code 0 (0x0).

My thought was that LINQ isn't using a thread pool but rather initializing new threads for each task.

To try and force the jobs onto the existing C# thread pool, I rewrote the iteration logic to use ThreadPool::QueueUserWorkItem(...) but this also resulted in threads exiting and a slight hiccup when threads were being created.

clusters.ForEach((item) => ThreadPool.QueueUserWorkItem(state => {
    item.Status = ClusterIsAccessible(item) ? Cluster.ConnectionStatus.Online : Cluster.ConnectionStatus.Offline;
}));

What am I doing wrong? I don't want to create unnecessary threads when there are already threads waiting for work.

CodePudding user response:

The problem is that the WaitOne is blocking a threadpool thread, which means a new one will be created after a short wait. The threadpool will continue creating more and more threads just to satisfy your requests, because each one keeps getting blocked.

Instead, you should use async await, which allows the threadpool thread to go off and do other things until the response comes back

public static async Task<bool> CanConnect(string host, int port, TimeSpan timeout)
{
    try
    {
        using (var cancel = new CancellationTokenSource(timeout))
        using (var client = new TcpClient())
        {
            await client.ConnectAsync(host, port, cancel.Token);
            System.Diagnostics.Debug.Print($"Success! {host}:{port}");
            return true;
        }
    }
    catch
    {
        System.Diagnostics.Debug.Print($"Failed connecting to {host}:{port}");
        return false;
    }
}

Then you call it not using Parallel.ForEach but using Task.WaitAll

await Task.WhenAll(
    clusters.Select(async item =>
        item.Status = (await ClusterIsAccessible(item)) ? Cluster.ConnectionStatus.Online : Cluster.ConnectionStatus.Offline)
  );

If you want to limit the number of tasks running at once, you can do this

var running = new List<Task>();
var completed = new List<Task>();

foreach (var cluster in clusters)
{
    if (running.Count == YourMaxCount)
    {
        var completedTask = await Task.WhenAny(running);
        running.Remove(completedTask);
        completed.Add(completedTask);
    }
    running.Add(Task.Run(async item =>
        item.Status = (await ClusterIsAccessible(item)) ? Cluster.ConnectionStatus.Online : Cluster.ConnectionStatus.Offline));
}

await Task.WhenAll(running);
completed.AddRange(running);
  • Related