我是Rx .NET的新手,但我认为我有一个商业场景。但是,在初始设计中,我仍然无法解决问题。
这似乎是反应性扩展的理想选择 - 我有:
这似乎我将一个项目列表作为一个observable,我从数据库中获取这些项目的“循环”过程会将它们“推”到这个observable中,然后对该observable的订阅将接管。
我希望有人可以帮助我分解,从概念上说,我正在寻找什么,所以我可以更好地解决它。谢谢!
这开始真的感觉好多了,但我知道这不太好。
public override async Task ProcessAsync(DataLoadRequest dataLoadRequest, Func<string, Task> createTrackingPayload)
{
_requestParameters = Deserialize<SchoolETLRequestParameters>(dataLoadRequest.DataExtractorParams);
WireUpDependencies();
//This is the new retriever which allows records to be "paged" (e.g. returns empty list for pageNum > 0 on the ones that don't have paging.)
_recordsToProcessRetriever = new SettingBasedRecordsRetriever(_propertyRepository, _requestParameters.RunType, _requestParameters.ResidentialProfileIDOverrides, _processorSettings.MaxBatchesToProcess, _etlLogger);
var query = Observable.Range(0, int.MaxValue)
.Select(pageNum => _recordsToProcessRetriever.GetResProfIDsToProcess(pageNum, _processorSettings.BatchSize))
.TakeWhile(resProfList => resProfList.Any())
.SelectMany(records => records)
.Select(resProf => Observable.Start(() => Task.Run(()=> _schoolDataProcessor.ProcessSchoolsAsync(resProf)).Result))
.Merge(maxConcurrent: _processorSettings.ParallelProperties);
var subscription = query.Subscribe(async trackingRequests =>
{
await CreateRequests(trackingRequests, createTrackingPayload);
var numberOfAttachments = SumOfRequestType(trackingRequests, TrackingRecordRequestType.AttachSchool);
var numberOfDetachments = SumOfRequestType(trackingRequests, TrackingRecordRequestType.DetachSchool);
var numberOfAssignmentTypeUpdates = SumOfRequestType(trackingRequests, TrackingRecordRequestType.UpdateAssignmentType);
_etlLogger.Info("Extractor generated {0} attachments, {1} detachments, and {2} assignment type changes.",
numberOfAttachments, numberOfDetachments, numberOfAssignmentTypeUpdates);
},
() =>
{
_etlLogger.Info("Finished! Woohoo!");
});
}
CreateTrackingRequests
)。 是否可以等待在此内完成的所有操作?
在这种情况下,我们不知道在运行时之前会产生什么可观察量。应用程序在命令中传递,该命令相当于:
前两个场景听起来像我可以很容易地将它们直接传递到一个没有问题的观察中。然而,在这种情况下,最后一个似乎我必须遍历一堆可观察对象,这不是我想要的行为(我希望所有600k项目最终都在一个大队列中并在一次)。
我的希望是我可以有一个方法“将事情排在队列中”,并让处理任务从50个批次中不断拉出。
注意:所有调用sprocs的方法都返回完全相同的东西 - IThing
列表(由于必要而混淆)。
我已将所有这些存储库函数等连接到我的处理器AS依赖项中,因此调用ProcessStuffForMyThing(List<IThing>)
负责整个过程,并使用相同的对象并行工作(不需要新建它)每次)。
答案 0 :(得分:2)
您应修复的代码存在许多问题。你已经多次看到过的错误 - 每个人似乎都走上了同样的道路。它真的归结为将你的思维从程序转变为功能。
首先,Rx拥有许多旨在让您的生活更轻松的运营商。其中一个是Observable.Using
。它的工作是在可观察的完成时旋转一次性资源,构建一个可观察的资源并处理资源。非常适合从数据库中读取记录。
您的代码似乎已经打开了数据库连接,并且您通过主题抽出记录。你应该避免使用外部状态(数据处理器),你应该避免使用主题。几乎总是可以使用的可观察操作员。
你正在做的另一件事你可能不应该混合你的monad - 或更具体的是可观察和任务。 Rx中有运算符用于将任务转换为可观察对象,但是它们用于与现有代码连接,不应该用作可观察对象中的工具。 rul是试图进入一个可观察的并留在那里,直到你准备好订阅你的数据。
我觉得你的代码有点零散,无法准确理解所谓的内容,所以我写了一个通用的代码,我认为它涵盖了你需要的内容。这是查询:
var pageSize = 4;
Func<Record, Result> process = r =>
{
Thread.Sleep(100); // Only here to demonstrate parallelism
return new Result(r.ID);
};
var query =
Observable
.Using(
() => new DataProcessor(),
dc =>
Observable
.Range(0, int.MaxValue)
.Select(n => dc.GetRecords(n, pageSize))
.TakeWhile(rs => rs.Any())
.SelectMany(rs => rs)
.Select(r => Observable.Start(() => process(r)))
.Merge(maxConcurrent: 4));
var subscription =
query
.Subscribe(
r => Console.WriteLine(r.ID),
() => Console.WriteLine("Done."));
我已经清楚地对你的代码采取了一些快捷方式,但实质上它是完全相同的(我希望)。
如果您添加以下类,则此代码可运行:
public class DataProcessor : IDisposable
{
public DataProcessor() { Console.WriteLine("Opened."); }
public void Dispose() { Console.WriteLine("Closed."); }
public IEnumerable<Record> GetRecords(int page, int count)
{
Console.WriteLine("Reading.");
Thread.Sleep(100);
var records = page <= 5
? Enumerable
.Range(0, count < 5 ? count : count / 2)
.Select(x => new Record())
.ToArray()
: new Record[] { };
Console.WriteLine("Read.");
return records;
}
}
public class Record
{
private static int __counter = 0;
public Record() { this.ID = __counter++; }
public int ID { get; private set; }
}
public class Result
{
public Result(int id) { this.ID = id; }
public int ID { get; private set; }
}
当我运行它时,我得到了这个结果:
Opened.
Reading.
Read.
Reading.
0
2
3
1
Read.
Reading.
7
Read.
5
6
4
Reading.
10
11
9
8
Read.
Reading.
15
12
Read.
14
Reading.
13
17
19
18
16
Read.
Reading.
21
Read.
20
22
23
Done.
Closed.
您可以看到它正在并行处理。你可以看到observable正在完成。您还可以看到数据库正在打开,然后在observable完成后关闭。
如果有帮助,请告诉我。
答案 1 :(得分:1)
首先,我不建议您滚动自己的枚举转换。如果您有IEnumerable<T>
,则可以使用.ToObservable()
扩展程序来处理枚举。
其次,你应该在Observable
方法中处理Subscribe
的结果,现在你的方法将在枚举后立即返回,因为你似乎没有真正等待你的任何事情。 async
方法。如果你必须使用当前的方法签名,那么你可以利用Observable也是等待的。
所以这是我建议的代码结构(警告未经测试):
public override async Task ProcessAsync(Request theRequest,
Func<string,Task> createTrackingPayload) // not my design^TM
{
// ...do stuff with the request, wire up some dependencies, etc.
//End goal is to call createTrackingPayload with some things.
await items.ToObservable()
.Select(thing => Observable.FromAsync(async () =>
{
var requests = await _dataProcessor.DoSomethingAsync(thing);
if (requests != null && requests.Any())
{
var numberOfType1 = SumOfRequestType(requests, TrackingRecordRequestType.Type1);
var numberOfType2 = SumOfRequestType(requests, TrackingRecordRequestType.DetachSchool);
var numberOfType3 = SumOfRequestType(requests, TrackingRecordRequestType.UpdateAssignmentType);
await CreateRequests(requests, createTrackingPayload); // something that will iterate over the list and call the function we need to call.
return requests.Count();
}
return 0;
}
}))
.Merge(maxConcurrent: _processorSettings.DegreeofParallelism)
.Do(x => _logger.Info("processed {0} items.", x))
.Aggregate(0, (acc, x) => acc + x);
}
这里的想法基本上是等待Observable的完成,它实际上会在Observable完成之前给你最后一个值。通过添加Do
和Aggregate
,您可以将记录逻辑移出处理逻辑。
答案 2 :(得分:0)
我在这里给予了Enigmativity,因为他们的回答是让我进入我(大部分)正确位置的原因。
我需要的代码如下所示,但One minor issue with the sequence evaluating multiple times除外。
var query = Observable.Range(0, int.MaxValue)
.Select(pageNum =>
{
_etlLogger.Info("Calling GetResProfIDsToProcess with pageNum of {0}", pageNum);
return _recordsToProcessRetriever.GetResProfIDsToProcess(pageNum, _processorSettings.BatchSize);
})
.TakeWhile(resProfList => resProfList.Any())
.SelectMany(records => records.Where(x=> _determiner.ShouldProcess(x)))
.Select(resProf => Observable.Start(async () => await _schoolDataProcessor.ProcessSchoolsAsync(resProf)))
.Merge(maxConcurrent: _processorSettings.ParallelProperties)
.Do(async trackingRequests =>
{
await CreateRequests(trackingRequests.Result, createTrackingPayload);
var numberOfAttachments = SumOfRequestType(trackingRequests.Result, TrackingRecordRequestType.AttachSchool);
var numberOfDetachments = SumOfRequestType(trackingRequests.Result, TrackingRecordRequestType.DetachSchool);
var numberOfAssignmentTypeUpdates = SumOfRequestType(trackingRequests.Result,
TrackingRecordRequestType.UpdateAssignmentType);
_etlLogger.Info("Extractor generated {0} attachments, {1} detachments, and {2} assignment type changes.",
numberOfAttachments, numberOfDetachments, numberOfAssignmentTypeUpdates);
});
var subscription = query.Subscribe(
trackingRequests =>
{
//Nothing really needs to happen here. Technically we're just doing something when it's done.
},
() =>
{
_etlLogger.Info("Finished! Woohoo!");
});
await query.Wait();