我正在了解TPL Dataflow
图书馆。到目前为止,这正是我所寻找的。
我创建了一个简单的类(下面),它执行以下功能
ImportPropertiesForBranch
后,我转到第三方api并获取属性列表Parallel.For
SendAsync
将属性数据导入我的propertyBufferBlock
propertyBufferBlock
与propertyXmlBlock
(本身为TransformBlock
)相关联。 propertyXmlBlock
然后(异步)返回API(使用属性数据中提供的api端点)并获取属性xml以进行反序列化。 TransformBlock
来将其保存到数据存储中。所以我的问题是;
await
内的TransformBlock
异步调用是否可以,或者这是一个
瓶颈?Parallel.For
中的BufferBlock
,TransformBlock
和异步的缓冲和异步性。我不确定它是最好的方式,我可能会混淆一些概念。欢迎任何指导,改进和陷阱建议。
using System.Diagnostics;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using My.Interfaces;
using My.XmlService.Models;
namespace My.ImportService
{
public class ImportService
{
private readonly IApiService _apiService;
private readonly IXmlService _xmlService;
private readonly IRepositoryService _repositoryService;
public ImportService(IApiService apiService,
IXmlService xmlService,
IRepositoryService repositoryService)
{
_apiService = apiService;
_xmlService = xmlService;
_repositoryService = repositoryService;
ConstructPipeline();
}
private BufferBlock<propertiesProperty> propertyBufferBlock;
private TransformBlock<propertiesProperty, string> propertyXmlBlock;
private TransformBlock<string, propertyType> propertyDeserializeBlock;
private ActionBlock<propertyType> propertyCompleteBlock;
public async Task<bool> ImportPropertiesForBranch(string branchName, int branchUrlId)
{
var propertyListXml = await _apiService.GetPropertyListAsync(branchUrlId);
if (string.IsNullOrEmpty(propertyListXml))
return false;
var properties = _xmlService.DeserializePropertyList(propertyListXml);
if (properties?.property == null || properties.property.Length == 0)
return false;
// limited to the first 20 for testing
Parallel.For(0, 20,
new ParallelOptions {MaxDegreeOfParallelism = 3},
i => propertyBufferBlock.SendAsync(properties.property[i]));
propertyBufferBlock.Complete();
await propertyCompleteBlock.Completion;
return true;
}
private void ConstructPipeline()
{
propertyBufferBlock = GetPropertyBuffer();
propertyXmlBlock = GetPropertyXmlBlock();
propertyDeserializeBlock = GetPropertyDeserializeBlock();
propertyCompleteBlock = GetPropertyCompleteBlock();
propertyBufferBlock.LinkTo(
propertyXmlBlock,
new DataflowLinkOptions {PropagateCompletion = true});
propertyXmlBlock.LinkTo(
propertyDeserializeBlock,
new DataflowLinkOptions {PropagateCompletion = true});
propertyDeserializeBlock.LinkTo(
propertyCompleteBlock,
new DataflowLinkOptions {PropagateCompletion = true});
}
private BufferBlock<propertiesProperty> GetPropertyBuffer()
{
return new BufferBlock<propertiesProperty>();
}
private TransformBlock<propertiesProperty, string> GetPropertyXmlBlock()
{
return new TransformBlock<propertiesProperty, string>(async propertiesProperty =>
{
Debug.WriteLine($"getting xml {propertiesProperty.prop_id}");
var propertyXml = await _apiService.GetXmlAsStringAsync(propertiesProperty.url);
return propertyXml;
},
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 1,
BoundedCapacity = 2
});
}
private TransformBlock<string, propertyType> GetPropertyDeserializeBlock()
{
return new TransformBlock<string, propertyType>(xmlAsString =>
{
Debug.WriteLine($"deserializing");
var propertyType = _xmlService.DeserializeProperty(xmlAsString);
return propertyType;
},
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 1,
BoundedCapacity = 2
});
}
private ActionBlock<propertyType> GetPropertyCompleteBlock()
{
return new ActionBlock<propertyType>(propertyType =>
{
Debug.WriteLine($"complete {propertyType.id}");
Debug.WriteLine(propertyType.address.display);
},
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 1,
BoundedCapacity = 2
});
}
}
}
答案 0 :(得分:2)
你实际上以错误的方式做了一些事情:
i => propertyBufferBlock.SendAsync(properties.property[i])
您需要使用await
方法,否则您将创建太多同步任务。
这一行:
MaxDegreeOfParallelism = 1
会将块的执行限制为随后的执行,这会降低您的性能。
正如您在评论中所说,您已切换为同步方法Post
,并通过设置BoundedCapacity
来限制块的容量。应谨慎使用此变体,因为您需要检查它的返回值,说明是否接受了消息。
至于你在等待块内async
方法的担心 - 这绝对没问题,应该像async
方法使用的其他情况那样完成。
答案 1 :(得分:1)
代码中是否存在潜在的瓶颈或区域可能会造成麻烦?
一般来说,您的方法看起来很好,潜在的瓶颈在于您使用MaxDegreeOfParallelism = 1
来限制块的并行处理。根据问题的描述,每个项目可以独立于其他项目进行处理,这就是为什么您可以一次处理多个项目。
是否可以等待
TransformBlock
内的异步调用,或者这是一个瓶颈?
完全没问题,因为TPL DataFlow支持异步操作。
虽然代码有效,但我担心
Parallel.For
中的BufferBlock
,TransformBlock
和异步的缓冲和异步性。我不确定它是最好的方式,我可能会混淆一些概念。
您的代码中可能会让您自己开枪的一个潜在问题是在Parallel.For
中调用异步方法,然后调用propertyBufferBlock.Complete();
。这里的问题是Parallel.For
不支持异步操作,并且调用它的方式将调用propertyBufferBlock.SendAsync
并在返回任务完成之前继续。这意味着,当Parallel.For
退出时,某些操作可能仍处于运行状态,并且尚未将项添加到缓冲区块中。然后,如果您将调用propertyBufferBlock.Complete();
,则这些待处理项将抛出异常,并且不会将项添加到处理中。您将获得未被观察到的异常。
您可以使用ForEachAsync
表单this blog post确保在完成阻止之前将所有项目添加到块中。但是,如果您仍然将处理限制为1次操作,则可以一次只添加一项。我不确定propertyBufferBlock.SendAsync
是如何实现的,但可能会在内部限制一次添加一个项目,因此并行添加没有任何意义。