C# 在.NET核心Web API中运行并行异步任务并返回结果
您好,最近我在.net核心web api项目中工作,该项目正在从外部api下载文件。 在这个.net核心api中,最近发现了一些问题,而文件的数量超过了100个。API最多下载50个文件,并跳过其他文件。WebAPI部署在AWS Lambda上,超时为15mnts 实际上,由于下载过程很长,操作正在超时C# 在.NET核心Web API中运行并行异步任务并返回结果,c#,.net-core,aws-lambda,task-parallel-library,asp.net-core-webapi,C#,.net Core,Aws Lambda,Task Parallel Library,Asp.net Core Webapi,您好,最近我在.net核心web api项目中工作,该项目正在从外部api下载文件。 在这个.net核心api中,最近发现了一些问题,而文件的数量超过了100个。API最多下载50个文件,并跳过其他文件。WebAPI部署在AWS Lambda上,超时为15mnts 实际上,由于下载过程很长,操作正在超时 public async Task<bool> DownloadAttachmentsAsync(List<DownloadAttachment> downloadAtt
public async Task<bool> DownloadAttachmentsAsync(List<DownloadAttachment> downloadAttachment)
{
try
{
bool DownloadFlag = false;
foreach (DownloadAttachment downloadAttachment in downloadAttachments)
{
DownloadFlag = await DownloadAttachment(downloadAttachment.id);
//update the download status in database
if(DownloadFlag)
{
bool UpdateFlag = await _DocumentService.UpdateDownloadStatus(downloadAttachment.id);
if (UpdateFlag)
{
await DeleteAttachment(downloadAttachment.id);
}
}
}
return true;
}
catch (Exception ext)
{
log.Error(ext, "Error in Saving attachment {attachemntId}",downloadAttachment.id);
return false;
}
}
这种方法行吗?如何提高性能?如何获得每个并行任务的结果,并根据成功标志更新到DB和delete?或者此错误是由于AWS超时造成的
如果将处理单个文件的代码提取到单独的方法,请提供帮助:
private async Task DownloadSingleAttachment(DownloadAttachment attachment)
{
try
{
var download = await DownloadAttachment(downloadAttachment.id);
if(download)
{
var update = await _DocumentService.UpdateDownloadStatus(downloadAttachment.id);
if (update)
{
await DeleteAttachment(downloadAttachment.id);
}
}
}
catch(....)
{
....
}
}
public async Task<bool> DownloadAttachmentsAsync(List<DownloadAttachment> downloadAttachment)
{
try
{
foreach (var attachment in downloadAttachments)
{
await DownloadSingleAttachment(attachment);
}
}
....
}
这些块现在可以链接到管道中并使用。设置PropagateCompletion=true
意味着一旦一个块完成处理,它将通知其所有连接的块也完成:
var linkOptions=new DataflowLinkOptions { PropagateCompletion = true};
downloader.LinkTo(updater, linkOptions);
updater.LinkTo(deleter,linkOptions);
只要我们需要,我们就可以将数据泵入头部模块。完成后,我们调用头块的Complete()
方法。当每个块完成其数据处理时,它将把其完成情况传播到管道中的下一个块。我们需要等待最后一个(尾部)块完成,以确保已处理所有附件:
foreach (var attachment in downloadAttachments)
{
await downloader.SendAsync(attachement.id);
}
downloader.Complete();
await deleter.Completion;
每个块都有一个输入和(必要时)一个输出缓冲区,这意味着消息的“生产者”和“消费者”不必同步,甚至不必相互了解。“生产商”需要知道的是在管道中的何处找到头块
节流和背压
限制的一种方法是通过MaxDegreeOfParallelism
使用固定数量的任务
还可以对输入缓冲区进行限制,从而在块不能足够快地处理消息时阻止前面的步骤或生产者。只需为块设置以下参数即可:
var dlOptions= new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 10,
BoundedCapacity=20,
};
var updaterOptions= new ExecutionDataflowBlockOptions
{
BoundedCapacity=20,
};
...
var downloader=new TransformBlock<...>(...,dlOptions);
var updater=new TransformBlock<...>(...,updaterOptions);
var dlOptions=新的ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism=10,
边界容量=20,
};
var updateoptions=新的ExecutionDataflowBlockOptions
{
边界容量=20,
};
...
var downloader=新TransformBlock(…,dlOptions);
var updater=newtransformblock(…,updateoptions);
如果将处理单个文件的代码提取到单独的方法,则无需进行其他更改:
private async Task DownloadSingleAttachment(DownloadAttachment attachment)
{
try
{
var download = await DownloadAttachment(downloadAttachment.id);
if(download)
{
var update = await _DocumentService.UpdateDownloadStatus(downloadAttachment.id);
if (update)
{
await DeleteAttachment(downloadAttachment.id);
}
}
}
catch(....)
{
....
}
}
public async Task<bool> DownloadAttachmentsAsync(List<DownloadAttachment> downloadAttachment)
{
try
{
foreach (var attachment in downloadAttachments)
{
await DownloadSingleAttachment(attachment);
}
}
....
}
这些块现在可以链接到管道中并使用。设置PropagateCompletion=true
意味着一旦一个块完成处理,它将通知其所有连接的块也完成:
var linkOptions=new DataflowLinkOptions { PropagateCompletion = true};
downloader.LinkTo(updater, linkOptions);
updater.LinkTo(deleter,linkOptions);
只要我们需要,我们就可以将数据泵入头部模块。完成后,我们调用头块的Complete()
方法。当每个块完成其数据处理时,它将把其完成情况传播到管道中的下一个块。我们需要等待最后一个(尾部)块完成,以确保已处理所有附件:
foreach (var attachment in downloadAttachments)
{
await downloader.SendAsync(attachement.id);
}
downloader.Complete();
await deleter.Completion;
每个块都有一个输入和(必要时)一个输出缓冲区,这意味着消息的“生产者”和“消费者”不必同步,甚至不必相互了解。“生产商”需要知道的是在管道中的何处找到头块
节流和背压
限制的一种方法是通过MaxDegreeOfParallelism
使用固定数量的任务
还可以对输入缓冲区进行限制,从而在块不能足够快地处理消息时阻止前面的步骤或生产者。只需为块设置以下参数即可:
var dlOptions= new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 10,
BoundedCapacity=20,
};
var updaterOptions= new ExecutionDataflowBlockOptions
{
BoundedCapacity=20,
};
...
var downloader=new TransformBlock<...>(...,dlOptions);
var updater=new TransformBlock<...>(...,updaterOptions);
var dlOptions=新的ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism=10,
边界容量=20,
};
var updateoptions=新的ExecutionDataflowBlockOptions
{
边界容量=20,
};
...
var downloader=新TransformBlock(…,dlOptions);
var updater=newtransformblock(…,updateoptions);
要运行多个异步操作,无需进行其他更改。您可以执行以下操作:
public async Task RunMultipleAsync<T>(IEnumerable<T> myList)
{
const int myNumberOfConcurrentOperations = 10;
var mySemaphore = new SemaphoreSlim(myNumberOfConcurrentOperations);
var tasks = new List<Task>();
foreach(var myItem in myList)
{
await mySemaphore.WaitAsync();
var task = RunOperation(myItem);
tasks.Add(task);
task.ContinueWith(t => mySemaphore.Release());
}
await Task.WhenAll(tasks);
}
private async Task RunOperation<T>(T myItem)
{
// Do stuff
}
public异步任务RunMultipleAsync(IEnumerable myList)
{
const int myNumberOfConcurrentOperations=10;
var mySemaphore=新信号量lim(myNumberOfConcurrentOperations);
var tasks=新列表();
foreach(myList中的var myItem)
{
等待mySemaphore.WaitAsync();
var任务=运行操作(myItem);
任务。添加(任务);
task.ContinueWith(t=>mySemaphore.Release());
}
等待任务。何时(任务);
}
专用异步任务运行操作(T myItem)
{
//做事
}
将DownloadAttachmentsAsync
中的代码放在“Do stuff”注释处
这将使用信号量来限制并发操作的数量,因为由于争用,运行多个并发操作通常是个坏主意。您需要进行实验,为您的用例找到最佳并发操作数。还请注意,为了使示例简短,省略了错误处理。要运行多个异步操作,可以执行以下操作:
public async Task RunMultipleAsync<T>(IEnumerable<T> myList)
{
const int myNumberOfConcurrentOperations = 10;
var mySemaphore = new SemaphoreSlim(myNumberOfConcurrentOperations);
var tasks = new List<Task>();
foreach(var myItem in myList)
{
await mySemaphore.WaitAsync();
var task = RunOperation(myItem);
tasks.Add(task);
task.ContinueWith(t => mySemaphore.Release());
}
await Task.WhenAll(tasks);
}
private async Task RunOperation<T>(T myItem)
{
// Do stuff
}
public异步任务RunMultipleAsync(IEnumerable myList)
{
const int myNumberOfConcurrentOperations=10;
var mySemaphore=新信号量lim(myNumberOfConcurrentOperations);
var tasks=新列表();
foreach(myList中的var myItem)
{
等待mySemaphore.WaitAsync();
var任务=运行操作(myItem);
任务。添加(任务);
task.ContinueWith(t=>mySemaphore.Release());
}
等待任务。何时(任务);
}
专用异步任务运行操作(T myItem)
{
//做事
}
将DownloadAttachmentsAsync
中的代码放在“Do stuff”注释处
这将使用信号量来限制并发操作的数量,因为运行多个并发操作通常是一个糟糕的ide
public async Task RunMultipleAsync<T>(IEnumerable<T> myList)
{
const int myNumberOfConcurrentOperations = 10;
var mySemaphore = new SemaphoreSlim(myNumberOfConcurrentOperations);
var tasks = new List<Task>();
foreach(var myItem in myList)
{
await mySemaphore.WaitAsync();
var task = RunOperation(myItem);
tasks.Add(task);
task.ContinueWith(t => mySemaphore.Release());
}
await Task.WhenAll(tasks);
}
private async Task RunOperation<T>(T myItem)
{
// Do stuff
}