我在Core .NET 2.2框架的顶部有一个使用C#
编写的控制台应用程序。
我的应用程序允许我使用Windows任务计划程序触发长期运行的管理作业。
其中一个管理员作业会进行Web-API调用,该调用会在将大量文件上传到Azure Blob存储之前下载大量文件。这是我的代码需要完成的逻辑步骤
MemoryStream
,以创建MemoryStream的集合一旦我有一个包含多个1000+ MemoryStream
的集合,我想将每个Stream
写入Azure Blob存储。由于写入远程存储的速度很慢,因此我希望可以使用其自己的进程或线程来执行每个写入迭代。这将使我可以同时并行运行潜在的1000多个线程,而不必等待每个写入操作的结果。每个线程将负责记录在写/上载过程中可能发生的任何错误。使用其他作业将处理所有记录的错误,因此我不必担心重试。
我的理解是,调用异步写入/上传流的代码将做到这一点。换句话说,我会说“有一个Stream
执行它并一直运行到需要的时间。只要任务完成,我就不在乎结果。”
在进行测试时,我发现我对调用async
的理解有些无效。我的印象是,当调用用async
定义的方法时,它将在后台线程/工作器中执行,直到该过程完成为止。但是,当我测试代码时,我的理解失败了。我的代码告诉我,如果不添加关键字await
,async
代码将永远不会真正执行。同时,添加关键字await
时,代码将等待直到过程完成执行后再继续。换句话说,为我的需要添加await
会达到异步调用该方法的目的。
这里是我的代码的精简版,目的是解释我要完成的工作
public async Task Run()
{
// This gets populated after calling the web-API and parsing out the result
List<Stream> files = new List<MemoryStream>{.....};
foreach (Stream file in files)
{
// This code should get executed in the background without having to await the result
await Upload(file);
}
}
// This method is responsible of upload a stream to a storage and log error if any
private async Task Upload(Stream stream)
{
try
{
await Storage.Create(file, GetUniqueName());
}
catch(Exception e)
{
// Log any errors
}
}
从上述代码中,调用await Upload(file);
可以正常工作,并且将按预期方式上传文件。但是,由于调用await
方法时使用的是Upload()
,因此在上载代码完成之前,我的循环不会跳转到下一个迭代。同时,删除await
关键字后,循环不会等待上传过程,但是Stream从未像我从未调用过代码那样实际写入存储。
如何并行执行多个Upload
方法,以使每个上载后台运行一个线程?
答案 0 :(得分:8)
将列表转换为“上传”任务列表,并使用Task.WhenAll()
等待所有任务:
public async Task Run()
{
// This gets populated after calling the web-API and parsing out the result
List<Stream> files = new List<MemoryStream>{.....};
var tasks = files.Select(Upload);
await Task.WhenAll(tasks);
}
有关任务/等待的更多信息,请参见this post。
答案 1 :(得分:4)
我希望可以使用自己的进程或线程来执行每个写迭代。
这并不是真正做到这一点的最佳方法。进程和线程是有限的资源。您的限制因素正在网络上等待执行操作。
您想要做的只是像这样:
var tasks = new List<Task>(queue.Count);
while (queue.Count > 0)
{
var myobject = Queue.Dequeue();
var task = blockBlob.UploadFromByteArrayAsync(myobject.content, 0, myobject.content.Length);
tasks.Add(task);
}
await Task.WhenAll(tasks);
在这里,我们只是在尽可能快地创建任务,然后等待所有任务完成。我们只让.Net框架负责其余的工作。
这里重要的是线程不会提高等待网络资源的速度。任务是从线程手中委派需要完成的工作的一种方法,因此您有更多的线程可以做任何事情(例如启动新的上传或对完成的上传做出响应)。如果线程只是等待上传完成,那是浪费的资源。
答案 2 :(得分:3)
您可能需要此:
var tasks = files.Select(Upload);
await Task.WhenAll(tasks);
请注意,它将生成与您拥有的文件一样多的任务,如果有太多的文件,则可能会使进程/机器崩溃。请参见Have a set of Tasks with only X running at a time作为解决该问题的示例。
答案 3 :(得分:3)
其他答案很好,但是另一种方法是在https://www.nuget.org/packages/System.Threading.Tasks.Dataflow/
的Nuget中使用您的 TPL DataFlowpublic static async Task DoWorkLoads(List<Something> results)
{
var options = new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 50
};
var block = new ActionBlock<Something>(MyMethodAsync, options);
foreach (var result in results)
block.Post(result );
block.Complete();
await block.Completion;
}
...
public async Task MyMethodAsync(Something result)
{
// Do async work here
}
数据流的优势
async
基于任务的解决方案一样,它自然可以与WhenAll
一起使用MaxDegreeOfParallelism
答案 4 :(得分:0)
您可以将代码转换为Azure Function,并让Azure处理大多数并行性,扩展并上传到Azure Blob存储工作。
您可以使用Http触发器或Service Bus触发器来启动每个下载,处理和上传任务。