我正在将代码从Salesforce转换为独立的.NET Core控制台应用程序,但我遇到了AWSSDK的问题。代码从S3获取图像,使用Kraken.io对它们进行优化,然后将它们放回S3。由于图片数量和文件大小较多,我尝试使用Parallel.ForEach
并行运行所有内容。
AWSSDK AmazonS3Client不喜欢在Parallel.ForEach
块中使用,即使我用using
块生成它的实例。我一直得到这个例外(尽管,在昨天的尝试中,这是" region"):
'已添加具有相同键的项目。关键:加速'
如果我在标准foreach
中运行完全相同的代码,它的工作原理非常好。现在我只是尝试测试一个对象是否存在于S3中,而且奇怪的是,如果我捕获来自AmazonS3Client
的异常,前八个总是失败,但所有后续实例和调用都有效。为什么会这样?
由于这是一个.NET Core控制台应用程序,因此AWSSDK程序包仅允许异步方法。不确定这是否与它有关。我对此表示怀疑。
任何帮助都将受到赞赏,因为我现在已经尝试将这项工作做了三天而且我已经忘记了,我让人们在我的脖子上呼吸工作
更新
由于代码被请求,我将尝试在此处放置尽可能多的内容,而不会泄露专有信息。
public sealed class Program {
public static void Main(
string[] args) {
using (var task = new XyzTask()) {
task.Execute();
}
}
}
public sealed class XyzTask :
TaskBase<XyzContext> {
public XyzTask() :
base(new XyzContext()) {
}
public override void Execute() {
var photos = Context.XyzPhotos.ToList();
if (photos.Count == 0) {
return;
}
// NOTE #1
Parallel.ForEach(photos, new ParallelOptions {
MaxDegreeOfParallelism = 1
}, p => {
using (var job = new XyzJob(p)) {
job.Execute();
}
});
}
}
public abstract class TaskBase<TContext> :
IDisposable
where TContext : DbContext {
protected TContext Context;
protected TaskBase(
TContext context) {
Context = context;
}
public abstract void Execute();
#region IDisposable
private bool _disposed;
protected virtual void Dispose(
bool disposing) {
if (_disposed) {
return;
}
if (disposing) {
if (Context != null) {
Context.Dispose();
Context = null;
}
}
_disposed = true;
}
public void Dispose() {
Dispose(true);
}
#endregion
}
注意#1:当MaxDegreeOfParallelism
设置为1
时,所有照片都会被处理。任何更高或未指定的内容都会导致最多前8张照片总是失败。
public sealed class XyzJob :
JobBase {
private AwsS3Client _awsS3;
private readonly Photo _p;
public XyzJob(
Photo p) {
_p = p;
_awsS3 = new AwsS3Client();
}
public override void Execute() {
var sourceExists = _awsS3.ExistsAsync("{bucket}", "{key}").Result;
if (!sourceExists) {
return;
}
var sourceBlob = _awsS3.GetAsync("{bucket}", "{key}").Result;
if (sourceBlob == null) {
return;
}
var sourceResult = GetOptimizedSource(_p, sourceBlob);
if (sourceResult == null) {
return;
}
var resizeResult = GetOptimizedResize(_p, sourceResult.FileBlob);
if (resizeResult == null) {
return;
}
var thumbnailResult = GetOptimizedThumbnail(_p, resizeResult.FileBlob);
if (thumbnailResult == null) {
return;
}
var sourcePutResult = _awsS3.PutAsync("{bucket}", "{key}", sourceResult.KeyName, sourceResult.FileBlob).Result;
if (!sourcePutResult) {
return;
}
var resizePutResult = _awsS3.PutAsync("{bucket}", "{key}", resizeResult.KeyName, resizeResult.FileBlob).Result;
if (!resizePutResult) {
return;
}
var thumnailPutResult = _awsS3.PutAsync("{bucket}", "{key}", thumbnailResult.KeyName, thumbnailResult.FileBlob).Result;
if (!thumnailPutResult) {
return;
}
_p.ResizedFile = new File {
Name = resizeResult.KeyName,
Size = resizeResult.FileSize,
S3Key = resizeResult.KeyName
};
_p.SourceFile.Size = sourceResult.FileSize;
_p.ThumbnailFile = new File {
Name = thumbnailResult.KeyName,
Size = thumbnailResult.FileSize,
S3Key = thumbnailResult.KeyName
};
}
}
public abstract class JobBase :
IDisposable {
public abstract void Execute();
#region IDisposable
private bool _disposed;
protected virtual void Dispose(
bool disposing) {
if (_disposed) {
return;
}
_disposed = true;
}
public void Dispose() {
Dispose(true);
}
#endregion
}
注意#2: 1)测试源(完全并行,前8个总是失败),2)获取源,3)优化源,4)优化源大小调整,5)从resize优化缩略图,6)put source,7)put resize,8)put thumbnail,9)完成所有操作后更新p
并退出。
问题始终来自AwsS3Client
实例,当它尝试实例化内部AmazonS3Client
时。我不打算显示KrakenClient
,因为它按预期工作,不是问题的根源。
public sealed class AwsS3Client :
ClientBase<AmazonS3Client> {
public AwsS3Client() :
base(new AmazonS3Client(
new BasicAWSCredentials(
ServiceCredentials.AwsAccessKey,
ServiceCredentials.AwsSecretKey
),
new AmazonS3Config {
RegionEndpoint = RegionEndpoint.Xyz,
UseAccelerateEndpoint = true
}
)) {
}
public async Task<bool> ExistsAsync(
string bucketName,
string keyName) {
return await Service.TestObjectExistsAsync(bucketName, keyName);
}
public async Task<byte[]> GetAsync(
string bucketName,
string keyName) {
using (var response = await Service.GetObjectAsync(bucketName, keyName))
using (var stream = new MemoryStream()) {
response.ResponseStream.CopyTo(stream);
return stream.ToArray();
}
}
public async Task<bool> PutAsync(
string bucketName,
string keyName,
string contentDisposition,
byte[] fileBlob) {
using (var stream = new MemoryStream(fileBlob)) {
var result = await Service.PutObjectAsync(new PutObjectRequest {
BucketName = bucketName,
Key = keyName,
InputStream = stream,
ServerSideEncryptionMethod = ServerSideEncryptionMethod.AES256,
Headers = {
ContentDisposition = $"inline; filename=\"{contentDisposition}\""
}
});
}
return await Service.TestObjectExistsAsync(bucketName, keyName);
}
}
public abstract class ClientBase<TService> :
IDisposable
where TService : class, IDisposable, new() {
protected TService Service;
protected ClientBase(
TService service) {
Service = service;
}
#region IDisposable Support
private bool _disposed;
protected virtual void Dispose(
bool disposing) {
if (_disposed) {
return;
}
if (disposing) {
if (Service != null) {
Service.Dispose();
Service = null;
}
}
_disposed = true;
}
public void Dispose() {
Dispose(true);
}
#endregion
}
internal static class AmazonS3ClientExtensions {
// NOTE #3
public static async Task<bool> TestObjectExistsAsync(
this AmazonS3Client client,
string bucketName,
string keyName) {
try {
await client.GetObjectMetadataAsync(new GetObjectMetadataRequest {
BucketName = bucketName,
Key = keyName
});
return true;
} catch {
return false;
}
}
}
注意#3:我相信这是问题通常发生的地方。如果我删除了try/catch
,那么在第一个8上它将抛出异常,并在此问题开头引用该消息。我发现这很奇怪,因为我希望它在AmazonS3Client
第一次实例时抛出。相反,它在第一次使用时会抛出(再次,它被调用的前8次,之后效果很好。)
所以,一切都是。同样,仅在前8个调用上抛出异常,之后它完全按预期工作。如果我将并行性降为1,则所有调用都按预期工作,但是我只是有一个同步循环,这违背了并行循环的目的。
另外,我之所以采用这种结构是有原因的,最终会添加其他任务和作业,我将通过从计划任务中传入args来触发。