C# 如何正确地将任务排队以在C中运行#
我有一个项目枚举(C# 如何正确地将任务排队以在C中运行#,c#,.net,multithreading,asynchronous,async-await,C#,.net,Multithreading,Asynchronous,Async Await,我有一个项目枚举(RunData.Demand),每个项目都表示一些涉及通过HTTP调用API的工作。如果我在每次迭代中都调用API,那么它的工作效果会非常好。但是,每次迭代都需要一两秒钟的时间,因此我希望运行2-3个线程,并在它们之间分配工作。以下是我正在做的: ThreadPool.SetMaxThreads(2, 5); // Trying to limit the amount of threads var tasks = RunData.Demand .Select(servic
RunData.Demand
),每个项目都表示一些涉及通过HTTP调用API的工作。如果我在每次迭代中都调用API,那么它的工作效果会非常好。但是,每次迭代都需要一两秒钟的时间,因此我希望运行2-3个线程,并在它们之间分配工作。以下是我正在做的:
ThreadPool.SetMaxThreads(2, 5); // Trying to limit the amount of threads
var tasks = RunData.Demand
.Select(service => Task.Run(async delegate
{
var availabilityResponse = await client.QueryAvailability(service);
// Do some other stuff, not really important
}));
await Task.WhenAll(tasks);
client.QueryAvailability
调用基本上使用HttpClient
类调用API:
public async Task<QueryAvailabilityResponse> QueryAvailability(QueryAvailabilityMultidayRequest request)
{
var response = await client.PostAsJsonAsync("api/queryavailabilitymultiday", request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsAsync<QueryAvailabilityResponse>();
}
throw new HttpException((int) response.StatusCode, response.ReasonPhrase);
}
公共异步任务QueryAvailability(QueryAvailabilityMultidayRequest请求)
{
var response=wait client.postsjsonasync(“api/queryavailabilitymultiday”,请求);
if(响应。IsSuccessStatusCode)
{
return wait response.Content.ReadAsAsync();
}
抛出新的HttpException((int)response.StatusCode,response.reasonPhase);
}
这在一段时间内效果很好,但最终事情开始超时。如果我将HttpClient超时设置为一个小时,那么我开始出现奇怪的内部服务器错误
我开始做的是在QueryAvailability
方法中设置秒表,以查看发生了什么
正在发生的是RunData中的所有1200个项目。立即创建需求,所有1200个等待客户端。正在调用PostAsJsonAsync
方法。看起来它然后使用2个线程来缓慢地检查任务,所以在接近结束时,我有一些任务已经等待了9或10分钟
以下是我想要的行为:
我想创建1200个任务,然后在线程可用时一次运行3-4个。我不想立即将1200个HTTP调用排队
有什么好办法可以做到这一点吗?正如我一直建议的那样。。您需要的是TPL数据流(要安装:
install Package System.Threading.Tasks.Dataflow
)
创建一个ActionBlock
,其中包含对每个项目执行的操作。为节流设置MaxDegreeOfParallelism
。开始向其发布并等待其完成:
var block = new ActionBlock<QueryAvailabilityMultidayRequest>(async service =>
{
var availabilityResponse = await client.QueryAvailability(service);
// ...
},
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 });
foreach (var service in RunData.Demand)
{
block.Post(service);
}
block.Complete();
await block.Completion;
var block=newactionblock(异步服务=>
{
var availabilityResponse=wait client.QueryAvailability(服务);
// ...
},
新的ExecutionDataflowBlockOptions{MaxDegreeOfParallelism=4});
foreach(RunData.Demand中的var服务)
{
街区邮政(服务);
}
block.Complete();
等待区块完成;
您使用的是异步HTTP调用,因此限制线程数量不会有帮助(答案之一是并行中的并行选项.MaxDegreeOfParallelism
。ForEach
)。即使是单个线程也可以启动所有请求并在它们到达时处理结果
解决这个问题的一种方法是使用TPL数据流
另一个很好的解决方案是将源IEnumerable
划分为多个分区,并按如下所述顺序处理每个分区中的项:
公共静态任务ForEachAsync(此IEnumerable源代码,int-dop,Func-body)
{
返回任务.WhenAll(
从Partitioner.Create(source).GetPartitions(dop)中的分区
选择Task.Run(异步委托
{
使用(分区)
while(partition.MoveNext())
等待体(partition.Current);
}));
}
虽然数据流库很棒,但我认为如果不使用块组合,它会有点沉重。我倾向于使用下面的扩展方法
此外,与Partitioner方法不同,它在调用上下文上运行异步方法-警告是,如果代码不是真正异步的,或者采用“快速路径”,那么它将有效地同步运行,因为没有显式创建线程
public static async Task RunParallelAsync<T>(this IEnumerable<T> items, Func<T, Task> asyncAction, int maxParallel)
{
var tasks = new List<Task>();
foreach (var item in items)
{
tasks.Add(asyncAction(item));
if (tasks.Count < maxParallel)
continue;
var notCompleted = tasks.Where(t => !t.IsCompleted).ToList();
if (notCompleted.Count >= maxParallel)
await Task.WhenAny(notCompleted);
}
await Task.WhenAll(tasks);
}
公共静态异步任务runparallelsync(此IEnumerable items,Func asyncAction,int maxParallel)
{
var tasks=新列表();
foreach(项目中的var项目)
{
添加(异步操作(项));
if(tasks.Count!t.IsCompleted).ToList();
如果(notCompleted.Count>=maxParallel)
等待任务。何时(未完成);
}
等待任务。何时(任务);
}
这是一个老问题,但我想提出一个使用该类的替代轻量级解决方案。只需参考System.Threading
SemaphoreSlim sem = new SemaphoreSlim(4,4);
foreach (var service in RunData.Demand)
{
await sem.WaitAsync();
Task t = Task.Run(async () =>
{
var availabilityResponse = await client.QueryAvailability(serviceCopy));
// do your other stuff here with the result of QueryAvailability
}
t.ContinueWith(sem.Release());
}
信号量充当锁定机制。您只能通过调用Wait(WaitAsync)来输入信号量,Wait(WaitAsync)从计数中减去一。调用release将向计数中添加一个 这看起来也很有希望,并且看起来它与
async
方法配合得很好(例如,我可以wait
on block completion)。。我要试一试。@MikeChristensen是的。它是.Net中为数不多的专门为异步等待而编写的库之一。工作完美无瑕!我现在是个粉丝了。接受。您似乎没有为每个呼叫创建新的客户端。您知道System.Net.Http.HttpClient
对于实例调用不是线程安全的吗?应为每个调用创建一个新实例(并在每次调用后处理)。QueryAvailability
方法实际上位于创建HttpClient
的类中,该类是该实例的私有成员。我不知道它不是线程安全的,但我肯定可以在每次调用之前创建它。我会进一步调查的,谢谢!嗯,我做了一些研究,看起来我所做的是线程安全的。请参阅,如果我理解正确,根据使用方法的不同,可以使用sem.Wait()
而不是Wait-sem.WaitASync()
。这样做会阻止调用线程,因此不应该在UI线程上执行,但在任何其他线程上,这可能是管理要完成的工作的最简单方法。具体而言,如果4个SEM中有一个可用,它将立即进行。否则,它将等待一个可用的列表。在foreach循环的每次迭代中创建不必要的列表。事件或事件
SemaphoreSlim sem = new SemaphoreSlim(4,4);
foreach (var service in RunData.Demand)
{
await sem.WaitAsync();
Task t = Task.Run(async () =>
{
var availabilityResponse = await client.QueryAvailability(serviceCopy));
// do your other stuff here with the result of QueryAvailability
}
t.ContinueWith(sem.Release());
}