我注意到我的进程中的初始减速,并且在进行多次挂起后,我能够隔离问题并使用以下代码重现场景。我正在使用一个具有锁定而不具有锁定的库,这最终会调用某些方法的用户端实现。这些方法使用httpclient进行异步调用。这些异步调用是从库内的这些锁中进行的。
现在,关于发生了什么的理论(如果我错了,请纠正我): 获得spun的任务尝试获取锁并保持足够快的线程,以便第一个PingAsync方法需要等待默认任务调度程序启动一个新线程以使其运行,这是0.5秒,基于默认的.net调度算法。这就是为什么我认为我发现总任务的延迟大于32,这也随着总任务数的增加而线性增加。
解决方法:
我知道结合asyc和非async是糟糕的设计和使用sempahores'异步调用将是一个更好的方法。假设我无法控制这个库,那么如何解决这个问题?
const int ParallelCount = 16;
const int TotalTasks = 33;
static object _lockObj = new object();
static HttpClient _httpClient = new HttpClient();
static int count = 0;
static void Main(string[] args)
{
ThreadPool.GetMinThreads(out int workerThreads, out int ioThreads);
Console.WriteLine($"Min threads count. Worker: {workerThreads}. IoThreads: {ioThreads}");
ThreadPool.GetMaxThreads(out workerThreads, out ioThreads);
Console.WriteLine($"Max threads count. Worker: {workerThreads}. IoThreads: {ioThreads}");
//var done = ThreadPool.SetMaxThreads(1024, 1000);
//ThreadPool.GetMaxThreads(out workerThreads, out ioThreads);
//Console.WriteLine($"Set Max Threads success? {done}.");
//Console.WriteLine($"Max threads count. Worker: {workerThreads}. IoThreads: {ioThreads}");
//var done = ThreadPool.SetMinThreads(1024, 1000);
//ThreadPool.GetMinThreads(out workerThreads, out ioThreads);
//Console.WriteLine($"Set Min Threads success? {done}.");
//Console.WriteLine($"Min threads count. Worker: {workerThreads}. IoThreads: {ioThreads}");
var startTime = DateTime.UtcNow;
var tasks = new List<Task>();
for (int i = 0; i < TotalTasks; i++)
{
tasks.Add(Task.Run(() => LibraryMethod()));
//while (tasks.Count > ParallelCount)
//{
// var task = Task.WhenAny(tasks.ToArray()).GetAwaiter().GetResult();
// if (task.IsFaulted)
// {
// throw task.Exception;
// }
// tasks.Remove(task);
//}
}
Task.WaitAll(tasks.ToArray());
//while (tasks.Count > 0)
//{
// var task = Task.WhenAny(tasks.ToArray()).GetAwaiter().GetResult();
// if (task.IsFaulted)
// {
// throw task.Exception;
// }
// tasks.Remove(task);
// Console.Write(".");
//}
Console.Write($"\nDone in {(DateTime.UtcNow-startTime).TotalMilliseconds}");
Console.ReadLine();
}
假设这是调用库方法的部分,
public static void LibraryMethod()
{
lock (_lockObj)
{
SimpleNonAsync();
}
}
最终,调用此方法的用户实现,这是异步。
public static void SimpleNonAsync()
{
//PingAsync().Result;
//PingAsync().ConfigureAwaiter(false).Wait();
PingAsync().Wait();
}
private static async Task PingAsync()
{
Console.Write($"{Interlocked.Increment(ref count)}.");
await _httpClient.SendAsync(new HttpRequestMessage
{
RequestUri = new Uri($@"http://127.0.0.1"),
Method = HttpMethod.Get
});
}
答案 0 :(得分:2)
这些异步调用是从库内的这些锁中进行的。
这是一个设计缺陷。在锁定时,没有人应该调用任意代码。
那就是说,锁与你所看到的问题无关。
我知道结合asyc和非async是糟糕的设计和使用sempahores&#39;异步调用将是一个更好的方法。假设我无法控制这个库,那么如何解决这个问题?
问题 表示库正在强制您的代码同步。这意味着每次下载都会阻止一个线程;只要图书馆的回调是同步的,就没有办法解决这个问题。
增加minthreads计数,我认为这是治疗症状,而不是实际问题。
如果您无法修改库,那么必须每个请求使用一个线程,这就成了一种可行的解决方法。您有来处理症状,因为您无法解决问题(即图书馆)。
另一种方法是使用有限的并发来控制触发的任务数。但这些是由网络服务器为传入的httprequests转换的任务,通常我们无法控制它(或者我们会吗?)
没有;导致问题的任务是您使用Task.Run
自行调整的问题。服务器上的任务完全独立;您的代码无法 影响甚至检测它们。
如果你想要更高的并发性而不等待线程注入,那么你需要增加最小线程,你也可能需要增加ServicePointManager.DefaultConnectionLimit
。然后,您可以继续使用Task.Run
或(我希望)Parallel
或并行LINQ进行并行处理。 Parallel
/ Parallel LINQ的一个不错的方面是它内置了对限制的支持,如果还需要的话。