我试图更好地理解线程,但遇到了让我感到困惑的事情。据我所知Task.Run()在另一个线程中启动任务。
我在下面构建了一些代码来对其进行测试,以查看其行为,但是我的理解存在漏洞。
我以为我可以像这样循环执行任务:
public void DoTheThings(List<string> inputList)
{
List<Task> taskList = new List<Task>();
foreach (var input in inputList)
{
taskList.Add(Task.Run(() => this.GetTheStuff(input)));
}
Task.WaitAll(taskList.ToArray());
Console.WriteLine("Queue completed");
}
如果调用的任务(GetTheStuff())延迟,则它将锁定该线程,因此下一个启动的任务将位于新线程中:
public async Task GetTheStuff(string input)
{
Console.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId + "starting");
int delay = GetRandomNumber(1000, 5000); // simulate time of a http request or something similar
var notInUse = input; // in real app this would be some useful assignment
await Task.Delay(delay);
Console.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId + "ending");
}
但这不会发生。相同的线程用于启动多个任务。大概是通过查看函数开头和结尾处的“ ManagedThreadID”来实现的。
在我的错误假设中,我认为Main()函数将是一个线程。它将为DoTheThings()启动一个新线程,然后此函数将为并发GetTheStuff()处理启动多个线程。
实际发生了什么?
完整的代码:
class Program
{
private static void Main(string[] args)
{
// build list of 100 random strings to represent input
List<string> thingsToProcess = new List<string>();
for (int i = 0; i < 100; i++)
{
thingsToProcess.Add(Path.GetRandomFileName());
}
Console.WriteLine("Starting queue");
var m = new MethodStuff();
var mainTask = Task.Run(() => m.DoTheThings(thingsToProcess));
Task.WaitAll(mainTask);
Console.WriteLine("All done");
Console.ReadLine();
}
}
class MethodStuff
{
private static readonly Random getrandom = new Random();
private static readonly object syncLock = new object();
public static int GetRandomNumber(int min, int max)
{
lock (syncLock)
{ // synchronize
return getrandom.Next(min, max);
}
}
// loop over all input and start each input in its own thread
public void DoTheThings(List<string> inputList)
{
List<Task> taskList = new List<Task>();
foreach (var input in inputList)
{
taskList.Add(Task.Run(() => this.GetTheStuff(input)));
}
Task.WaitAll(taskList.ToArray());
Console.WriteLine("Queue completed");
}
public async Task GetTheStuff(string input)
{
Console.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId + "starting");
int delay = GetRandomNumber(1000, 5000); // simulate time of a http request or something similar
var notInUse = input; // in real app this would be some useful assignment
await Task.Delay(delay);
Console.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId + "ending");
}
}
您真的应该问一个有关您实际上要解决的问题的问题:)
据我所知,您可能正在发出同步HTTP请求,并尝试通过将其触发来并行化它们Task.Run
。这会将它们排队到Threadpool,该线程池最初可能只包含与计算机上的vcore一样多的线程。假设您的HTTP请求是同步发出的,这将占用运行请求的池线程,直到请求完成。当达到与池中线程数相同的任务数时,队列将暂停,直到ThreadPool池任务完成或ThreadPool决定启动另一个线程。ThreadPool不会在任何紧急情况下启动线程,因此这会给方程式带来各种延迟。
获得高吞吐量的一个很好的经验法则是永远不要将阻塞的工作负载放到ThreadPool中。同步HTTP是一个阻塞的工作负载。
您应该切换异步Web请求,最好使用基于任务的异步和async / await关键字。如果做得正确,您将能够在不消耗ThreadPool的情况下触发数千个请求(尽管您的网络设备可能开始发牢骚……SOHO路由器对于这种情况非常不利)。
可能阻止高吞吐量的其他问题:
如果您从许多不同的主机发出请求,则可能选择使用第三方dns库,因为Web请求中的.Net DNS查找阶段始终同步运行。这太烂了。现在,您可以在HttpWebRequest中使用从库返回的IP地址,并将Host
属性手动设置为您要访问的主机的名称。我发现这可以大大改善HTTP请求的性能。
如果要向同一主机发出大量请求,则可能需要进行调整,ServicePointManager.DefaultConnectionLimit
以便可以一次向一个主机发出两个(或6个以上,具体取决于上下文)请求。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句