繁体   English   中英

重构实体方法避免并发问题

[英]Refactoring entity method to avoid concurrency problems

我正在尝试使用DDD的项目即将结束,但是发现了一个明显的错误,不确定如何轻松解决。

这是我的实体-为简单起见,我将其简化为:

public class Contribution : Entity
{
    protected Contribution()
    {
        this.Parts = new List<ContributionPart>();
    }

    internal Contribution(Guid id)
    {
        this.Id = id;
        this.Parts = new List<ContributionPart>();
    }

    public Guid Id { get; private set; }

    protected virtual IList<ContributionPart> Parts { get; private set; }

    public void UploadParts(string path, IEnumerable<long> partLengths)
    {
        if (this.Parts.Count > 0)
        {
            throw new InvalidOperationException("Parts have already been uploaded.");
        }

        long startPosition = 0;
        int partNumber = 1;

        foreach (long partLength in partLengths)
        {
            this.Parts.Add(new ContributionPart(this.Id, partNumber, partLength));
            this.Commands.Add(new UploadContributionPartCommand(this.Id, partNumber, path, startPosition, partLength));
            startPosition += partLength;
            partNumber++;
        }
    }

    public void SetUploadResult(int partNumber, string etag)
    {
        if (etag == null)
        {
            throw new ArgumentNullException(nameof(etag));
        }

        ContributionPart part = this.Parts.SingleOrDefault(p => p.PartNumber == partNumber);

        if (part == null)
        {
            throw new ContributionPartNotFoundException(this.Id, partNumber);
        }

        part.SetUploadResult(etag);

        if (this.Parts.All(p => p.IsUploaded))
        {
            IEnumerable<PartUploadedResult> results = this.Parts.Select(p => new PartUploadedResult(p.PartNumber, p.ETag));
            this.Events.Add(new ContributionUploaded(this.Id, results));
        }
    }
}

我的错误发生在SetUploadResult方法中。 基本上,多个线程正在同时执行上载,然后在上载结束时调用SetUploadResult。 但是由于该实体是事先加载的,因此每个线程将在该实体的另一个实例上调用SetUploadResult,因此测试if (this.Parts.All(p => p.IsUploaded)永远不会达到true。

我不确定如何轻松解决此问题。 向Commands集合中添加多个UploadContributionPartCommands的想法是,每个ContributionPart可以并行上传-我的CommandBus确保了这一点-但是每个部分并行上传时,这会导致我的实体逻辑出现问题。

我认为您可以重构Contribution以便它不会处理SetUploadResult 它将解耦Contribution实体,并且SetUploadResult被隔离,从而将技术问题排除在Contribution域模型之外。

创建一个包含SetUploadResult在做什么的调度程序类。

一旦Contribution实体完成了其逻辑,执行线程将返回到应用程序服务。 此时,可以将来自实体的事件馈送到调度程序中。

如果它们是长时间运行的过程,则可以将它们添加为任务集合并异步运行它们。 然后,您可以等待所有任务完成。 您可以在SO中搜索如何执行此操作。

var results = await Task.WhenAll(task1, task2,...taskN);

如果多个线程可能同时调用SetUploadResult方法,并且您遇到了竞争状况,则应使用同步机制(例如锁)来保护关键部分: https : //msdn.microsoft.com/zh-cn/library/c5kehkcz.aspx

如果将锁定字段设为static则它将在您实体类型的所有实例之间共享,例如:

private static readonly object _lock = new object();
public void SetUploadResult(int partNumber, string etag)
{
    if (etag == null)
    {
        throw new ArgumentNullException(nameof(etag));
    }

    ContributionPart part = this.Parts.SingleOrDefault(p => p.PartNumber == partNumber);

    if (part == null)
    {
        throw new ContributionPartNotFoundException(this.Id, partNumber);
    }

    part.SetUploadResult(etag);

    lock (_lock) //Only one thread at a time can enter this critical section.
                 //The second thread will wait here until the first thread leaves the critical section.
    {
        if (this.Parts.All(p => p.IsUploaded))
        {
            IEnumerable<PartUploadedResult> results = this.Parts.Select(p => new PartUploadedResult(p.PartNumber, p.ETag));
            this.Events.Add(new ContributionUploaded(this.Id, results));
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM