Ste*_*eve 3 c# optimization task-parallel-library asp.net-web-api
我有一个进程,我需要对服务器进行约 100 个 http api 调用并处理结果。我已经将这个 commandexecutor 放在一起,它构建了一个命令列表,然后异步运行它们。进行大约 100 次调用并解析结果需要 1 多分钟。使用浏览器的 1 个请求在大约 100 毫秒内给我一个响应。你会认为大约 100 次调用大约需要 10 秒。我相信我做错了什么,这应该会更快。
public static class CommandExecutor
{
private static readonly ThreadLocal<List<Command>> CommandsToExecute =
new ThreadLocal<List<Command>>(() => new List<Command>());
private static readonly ThreadLocal<List<Task<List<Candidate>>>> Tasks =
new ThreadLocal<List<Task<List<Candidate>>>>(() => new List<Task<List<Candidate>>>());
public static void ExecuteLater(Command command)
{
CommandsToExecute.Value.Add(command);
}
public static void StartExecuting()
{
foreach (var command in CommandsToExecute.Value)
{
Tasks.Value.Add(Task.Factory.StartNew<List<Candidate>>(command.GetResult));
}
Task.WaitAll(Tasks.Value.ToArray());
}
public static List<Candidate> Result()
{
return Tasks.Value.Where(x => x.Result != null)
.SelectMany(x => x.Result)
.ToList();
}
}
Run Code Online (Sandbox Code Playgroud)
我传递到此列表中的命令创建了一个新的 httpclient,使用 url 调用该客户端上的 getasync,将字符串响应转换为一个对象,然后对一个字段进行水合。
protected void Initialize()
{
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
}
protected override void Execute()
{
Initialize();
var task = _httpClient.GetAsync(string.Format(Url, Input));
Result = ConvertResponseToObjectAsync(task).Result;
Result.ForEach(x => x.prop = value);
}
private static Task<Model> ConvertResponseToObjectAsync(Task<HttpResponseMessage> task)
{
return task.Result.Content.ReadAsAsync<Model>(
new MediaTypeFormatter[]
{
new Formatter()
});
}
Run Code Online (Sandbox Code Playgroud)
您能否解决我的瓶颈或对如何加快速度有任何建议。
进行这些更改的编辑使其缩短到 4 秒。
protected override void Execute()
{
Initialize();
_httpClient.GetAsync(string.Format(Url, Input))
.ContinueWith(httpResponse => ConvertResponseToObjectAsync(httpResponse)
.ContinueWith(ProcessResult));
}
protected void ProcessResult(Task<Model> model)
{
Result = model.Result;
Result.ForEach(x => x.prop = value);
}
Run Code Online (Sandbox Code Playgroud)
停止创建新的 HttpClient 实例。每次处理 HttpClient 实例时,它都会关闭 TCP/IP 连接。创建一个 HttpClient 实例并为每个请求重复使用它。HttpClient 可以同时在多个不同的线程上发出多个请求。
避免在 ConvertResponseToObjectAsync 中使用task.Result,然后再次在Execute. 相反,将它们链接到原始GetAsync任务上ContinueWith。
就目前而言,Result将阻止当前线程的执行,直到其他任务完成。但是,您的线程池将很快被等待其他无处运行的任务的任务所支持。最终(等待一秒钟后),线程池将添加一个额外的线程来运行,因此这最终会完成,但效率很低。
作为一般原则,Task.Result除了任务延续之外,您应该避免访问。
作为奖励,您可能不想使用ThreadLocalStorage. ThreadLocalStorage将存储在其中的项目的实例存储在访问它的每个线程上。在这种情况下,您似乎想要一种线程安全但共享的存储形式。我会推荐ConcurrentQueue这种事情。