[英]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.