[英]Parallel batch file download from Amazon S3 using AWS S3 SDK for .NET
问题:我想使用他们的.NET SDK从AWS S3并行下载100个文件。 下载的内容应存储在100个内存流中(文件足够小,我可以从那里获取)。 我在Task,IAsyncResult,Parallel。*和.NET 4.0中的其他不同方法之间感到困惑。
如果我试图自己解决这个问题 ,我会想象这样的伪代码:(编辑为某些变量添加类型)
using Amazon;
using Amazon.S3;
using Amazon.S3.Model;
AmazonS3 _s3 = ...;
IEnumerable<GetObjectRequest> requestObjects = ...;
// Prepare to launch requests
var asyncRequests = from rq in requestObjects
select _s3.BeginGetObject(rq,null,null);
// Launch requests
var asyncRequestsLaunched = asyncRequests.ToList();
// Prepare to finish requests
var responses = from rq in asyncRequestsLaunched
select _s3.EndGetRequest(rq);
// Finish requests
var actualResponses = responses.ToList();
// Fetch data
var data = actualResponses.Select(rp => {
var ms = new MemoryStream();
rp.ResponseStream.CopyTo(ms);
return ms;
});
此代码并行启动100个请求,这很好。 但是,有两个问题:
所以在这里我开始想我正在走错路......
救命?
如果将操作分解为一个异步处理一个请求然后再调用100次的方法,则可能更容易。
首先,让我们确定您想要的最终结果。 因为您将使用的是MemoryStream
这意味着您将要从您的方法返回Task<MemoryStream>
。 签名看起来像这样:
static Task<MemoryStream> GetMemoryStreamAsync(AmazonS3 s3,
GetObjectRequest request)
因为您的AmazonS3
对象实现了异步设计模式 ,所以您可以使用TaskFactory
类上的FromAsync
方法从实现异步设计模式的类生成Task<T>
,如下所示:
static Task<MemoryStream> GetMemoryStreamAsync(AmazonS3 s3,
GetObjectRequest request)
{
Task<GetObjectResponse> response =
Task.Factory.FromAsync<GetObjectRequest,GetObjectResponse>(
s3.BeginGetObject, s3.EndGetObject, request, null);
// But what goes here?
所以你已经处在一个好地方,你有一个Task<T>
,你可以等待,或者在通话结束时收到回叫。 但是,你需要以某种方式翻译GetObjectResponse
从调用返回的Task<GetObjectResponse>
成MemoryStream
。
为此,您希望在Task<T>
类上使用ContinueWith
方法 。 可以把它想象成Enumerable
类的Select
方法的异步版本,它只是对另一个Task<T>
的投影,除了每次调用ContinueWith
,你可能会创建一个运行该段代码的新任务。
有了它,您的方法如下所示:
static Task<MemoryStream> GetMemoryStreamAsync(AmazonS3 s3,
GetObjectRequest request)
{
// Start the task of downloading.
Task<GetObjectResponse> response =
Task.Factory.FromAsync<GetObjectRequest,GetObjectResponse>(
s3.BeginGetObject, s3.EndGetObject, request, null
);
// Translate.
Task<MemoryStream> translation = response.ContinueWith(t => {
using (Task<GetObjectResponse> resp = t ){
var ms = new MemoryStream();
t.Result.ResponseStream.CopyTo(ms);
return ms;
}
});
// Return the full task chain.
return translation;
}
请注意,在上面你可以调用ContinueWith
的重载来传递TaskContinuationOptions.ExecuteSynchronously
,因为看起来你做的工作很少(我不知道,响应可能很大 )。 如果您正在进行非常小的工作而不必为了完成工作而启动新任务,则应该传递TaskContinuationOptions.ExecuteSynchronously
这样您就不会浪费时间为最少的操作创建新任务。
现在您已经拥有了可以将一个请求转换为Task<MemoryStream>
,创建一个可以处理任意数量的包装器的包装器很简单:
static Task<MemoryStream>[] GetMemoryStreamsAsync(AmazonS3 s3,
IEnumerable<GetObjectRequest> requests)
{
// Just call Select on the requests, passing our translation into
// a Task<MemoryStream>.
// Also, materialize here, so that the tasks are "hot" when
// returned.
return requests.Select(r => GetMemoryStreamAsync(s3, r)).
ToArray();
}
在上面,您只需获取一系列GetObjectRequest
实例,它将返回一个Task<MemoryStream>
数组。 返回物化序列的事实很重要。 如果在返回之前没有实现它,则在迭代序列之前不会创建任务。
当然,如果你想要这种行为,那么无论如何,只需删除对.ToArray()
的调用,让方法返回IEnumerable<Task<MemoryStream>>
,然后在迭代完成任务时进行请求。
从那里,您可以一次处理一个(在循环中使用Task.WaitAny
方法 )或等待所有这些完成(通过调用Task.WaitAll
方法 )。 后者的一个例子是:
static IList<MemoryStream> GetMemoryStreams(AmazonS3 s3,
IEnumerable<GetObjectRequest> requests)
{
Task<MemoryStream>[] tasks = GetMemoryStreamsAsync(s3, requests);
Task.WaitAll(tasks);
return tasks.Select(t => t.Result).ToList();
}
此外,应该提到的是,这非常适合Reactive Extensions框架 ,因为它非常适合IObservable<T>
实现。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.