.net 在ForEachAsync中使用Task.Run可以吗?

.net 在ForEachAsync中使用Task.Run可以吗?,.net,multithreading,parallel-processing,async-await,.net,Multithreading,Parallel Processing,Async Await,我们最初使用的是ForEachAsync方法(在他的blob底部) 公共静态异步任务ForEachAsync( 此IEnumerable源,int degreeOfParallelism,函数体,Action handleException=null) { if(source.Any()) { 等待任务( 从Partitioner.Create(source.GetPartitions)中的分区(degreeOfParallelism) 选择Task.Run(异步委托 { 使用(分区) whil

我们最初使用的是ForEachAsync方法(在他的blob底部)

公共静态异步任务ForEachAsync(
此IEnumerable源,int degreeOfParallelism,函数体,Action handleException=null)
{
if(source.Any())
{
等待任务(
从Partitioner.Create(source.GetPartitions)中的分区(degreeOfParallelism)
选择Task.Run(异步委托
{
使用(分区)
while(partition.MoveNext())
等待正文(partition.Current).ContinueWith(t=>
{
//观察例外情况
如果(t.IsFaulted)
{
handleException?.Invoke(t);
}
});
}));
}
}
但我们的一位同事对Stephen Cleary系列文章中描述的Task.Run开销有一个担忧

使用wait with Task时,会(至少)引入四个效率问题。在ASP.NET中运行:
•额外(不必要)螺纹 切换到任务。运行线程池线程。同样,当 线程完成请求时,必须输入请求上下文 (这不是实际的线程切换,但有开销)。
•创建额外(不必要)垃圾。异步编程是一种 权衡:您以更高的响应速度为代价获得了更高的响应速度 内存使用。在这种情况下,您最终会为 完全不必要的异步操作。 •ASP.NET 线程池启发被任务抛出。运行“意外” 借用线程池线程。我在这里没有很多经验, 但我的直觉告诉我,启发式应该恢复得很好 如果意外任务确实很短,并且不会按要求处理它 如果意外任务持续两秒以上,则表现优雅。
•ASP.NET无法提前终止请求,即如果 客户端断开连接或请求超时。在同步情况下, ASP.NET知道请求线程,因此可以中止它。在 异步情况下,ASP.NET不知道其他辅助线程池 线程是该请求的“for”。可以通过使用 取消令牌,但这超出了本文的范围

我的问题是使用Task.RunforForEachAsync可以吗?还是有更好的方法可以使用受控狗并行运行多个异步任务(并行度)? 例如,我想处理400个项目,bun并行运行不超过100个项目

我们在.Net和.Net核心环境中都使用ForEachAsync方法,因此,如果不同环境的答案不同,我很乐意知道这两种方法

更新以澄清我们正在使用的技术:
我们有windows服务/控制台(用.Net4.6.1编写),可以从数据库中读取数千条记录,然后将它们分别并行发布(例如dop=100)到web api服务(我们考虑过分批发送,但尚未实现)。

我们还有Asp.Net核心服务和后台托管服务,定期(例如,每10秒)提取一页项目(例如,最多400个),然后并行(例如,dop=100)将其保存到单个Azure Blob。

以异步方式处理MDOP为100的400条消息的简单方法是使用一个。类似这样的方法会奏效:

public class ActionBlockExample
{
    private ActionBlock<int> actionBlock;

    public ActionBlockExample()
    {
        actionBlock = new ActionBlock<int>(x => ProcessMsg(x), new ExecutionDataflowBlockOptions()
        {
            MaxDegreeOfParallelism = 100
        });
    }

    public async Task Process()
    {
        foreach (var msg in Enumerable.Range(0, 400))
        {
            await actionBlock.SendAsync(msg);
        }
        actionBlock.Complete();
        await actionBlock.Completion;
    }

    private Task ProcessMsg(int msg) => Task.Delay(100);
}
公共类ActionBlockExample
{
私有ActionBlock ActionBlock;
公共行动块示例()
{
actionBlock=new actionBlock(x=>ProcessMsg(x),new ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism=100
});
}
公共异步任务进程()
{
foreach(枚举范围(0400)中的var msg)
{
等待actionBlock.SendAsync(msg);
}
actionBlock.Complete();
等待动作块完成;
}
私有任务进程msg(int msg)=>Task.Delay(100);
}

默认情况下,
ActionBlock
有一个未绑定的输入缓冲区,它最多并行处理100条消息。不需要执行
任务。请在此处运行
,因为所有消息都在后台处理。

您实际上是在ASP.NET上吗?我强烈避免执行
任务。在ASP.NET上运行
和并行处理(包括
ForEachAsync
的此实现)。如果此代码将在ASP.NET上使用,您的同事的担心是合理的,在ASP.NET上,调用方可能希望使用
degreeOfParallelism:1
关闭并行。此代码将产生Stephen Cleary在其博客中列出的所有不必要的开销。@StephenCleary,我们在Asp.Net Core IHostedService上使用并行处理,但在http请求上不使用并行处理-请参阅我的问题更新谢谢,我已经用您建议的ActionBlock运行了一些测试,并与ForEachAsync进行了比较,但结果不是决定性的(有时一个更快,有时另一个更快),差别也不大。
public class ActionBlockExample
{
    private ActionBlock<int> actionBlock;

    public ActionBlockExample()
    {
        actionBlock = new ActionBlock<int>(x => ProcessMsg(x), new ExecutionDataflowBlockOptions()
        {
            MaxDegreeOfParallelism = 100
        });
    }

    public async Task Process()
    {
        foreach (var msg in Enumerable.Range(0, 400))
        {
            await actionBlock.SendAsync(msg);
        }
        actionBlock.Complete();
        await actionBlock.Completion;
    }

    private Task ProcessMsg(int msg) => Task.Delay(100);
}