线程池中排队的任务正在退出,不会释放回池中



我正在编写一个C#Blazor MAUI应用程序,并尝试执行一些自动化的后台任务。每隔30秒,我的应用程序就会扫描配置,以便在一组用户定义的服务器上执行连接测试。

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;
}
}

当我的调度程序开始对这些任务进行排队时,我注意到GUI上出现了一个非常轻微(但很明显(的问题。

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

我相信打嗝是创建线程的结果。我注意到,当作业完成时,用于扫描连接的线程将在20-25秒后退出/超时。

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).

我的想法是LINQ不使用线程池,而是为每个任务初始化新线程。

为了尝试将作业强制到现有的C#线程池中,我重写了迭代逻辑以使用ThreadPool::QueueUserWorkItem(...),但这也导致线程退出,并在创建线程时出现轻微的停顿。

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

我做错了什么?当已经有线程在等待工作时,我不想创建不必要的线程。

问题是WaitOne阻塞了一个线程池线程,这意味着在短时间等待后将创建一个新线程。线程池将继续创建越来越多的线程来满足您的请求,因为每个线程都会被阻塞。

相反,您应该使用asyncawait,它允许线程池线程关闭并做其他事情,直到响应返回

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;
}
}

然后你不使用Parallel.ForEach,而是使用Task.WaitAll

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

如果你想限制一次运行的任务数量,你可以做这个

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);

最新更新