具有多个AmazonS3Client实例的C#Parallel.ForEach异常

时间:2017-09-23 02:19:50

标签: c# amazon-s3 foreach parallel-processing aws-sdk

我正在将代码从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来触发。

0 个答案:

没有答案