我有一个填充的数据库目录,以及一个可用于检索对象的游标。这个目录显然可能非常大,我想要做的是使用 ReactiveUI 缓冲数据,同时保持UI数据绑定和响应。我按照here步骤将IEnumerable
翻译成IObservable
,如下所示:
public class CatalogService
{
...
public IObservable<DbObject> DataSource
{
get
{
return Observable.Create<DbObject>(obs =>
{
var cursor = Database.Instance.GetAllObjects();
var status = cursor.MoveToFirst();
while (status == DbStatus.OK)
{
var dbObject= Db.Create(cursor);
obs.OnNext(dbObject);
status = cursor.MoveToNext();
}
obs.OnCompleted();
return Disposable.Empty;
});
}
}
}
在我的视图类(特别是Loaded
事件)中,我订阅数据源并使用缓冲区方法,希望保持UI响应。
public ObservableCollection<DbObject> DbObjects { get; set; }
private async void OnLoad(object sender, RoutedEventArgs e)
{
var observableData = CatalogService.Instance.DataSource.Publish();
var chunked = observableData.Buffer(TimeSpan.FromMilliseconds(100));
var dispatcherObs = chunked.ObserveOnDispatcher(DispatcherPriority.Background);
dispatcherObs.Subscribe(dbObjects =>
{
foreach (var dbObject in dbObjects)
{
DbObjects.Add(dbObject);
}
});
await Task.Run(() => observableData.Connect());
await dispatcherObs.ToTask();
}
遗憾的是,结果恰恰相反。当我的视图控件(包含与ListBox
属性绑定的简单DbObjects
数据)加载时,在枚举整个目录之前,它不会显示任何数据。只有这样才能刷新UI。
我是ReactiveUI的新手,但我确信它能胜任手头的任务。如果我使用不正确,有没有人有任何建议或指示?
答案 0 :(得分:2)
有待进一步的信息,我的猜测是你可能有几个零长度的缓冲区,具体取决于数据库查询需要多长时间,然后是一个包含所有结果的非零长度缓冲区。你可能最好不要根据长度和时间限制缓冲区大小。
编辑 - 我只是想对原始实现中涉及的各种线程进行分析。我不同意Paul的分析,我不相信UI线程因数据库查询而被阻止。我认为由于大量结果被缓冲而被阻止。
查理 - 拜托,你能用代码(而不是调试器)计算数据库查询并转储你得到的缓冲区长度。
我将注释代码以显示所涉及的所有三个线程的顺序:
首先,在提供的代码之外,我假设通过OnLoad
事件致电Loaded
。
(1) - UI线程调用OnLoad
public ObservableCollection<DbObject> DbObjects { get; set; }
private async void OnLoad(object sender, RoutedEventArgs e)
{
// (2) UI Thread enters OnLoad
var observableData = CatalogService.Instance.DataSource.Publish();
var chunked = observableData
// (6) Thread A OnNext passes into Buffer
.Buffer(TimeSpan.FromMilliseconds(100));
// (7) Thread B, threadpool thread used by Buffer to run timer
var dispatcherObs = chunked
// (8) Thread B still
.ObserveOnDispatcher(DispatcherPriority.Background);
// (9) Non blocking OnNexts back to UI Thread
dispatcherObs.Subscribe(dbObjects =>
{
// (10) UI Thread receives buffered dbObjects
foreach (var dbObject in dbObjects)
{
// (11) UI Thread hurting while all these images are
// stuffed in the collection in one go - This is the issue I bet.
DbObjects.Add(dbObject);
}
});
await Task.Run(() =>
{
// (3) Thread A - a threadpool thread,
// triggers subscription to DataSource
// UI Thread is *NOT BLOCKED* due to await
observableData.Connect()
});
// (13) UI Thread - Dispatcher call back here at end of Create call
// BUT UI THREAD WAS NOT BLOCKED!!!
// (14) UI Thread - This task will be already completed
// It is causing a second subscription to the already completed published observable
await dispatcherObs.ToTask();
}
public class CatalogService
{
...
public IObservable<DbObject> DataSource
{
get
{
return Observable.Create<DbObject>(obs =>
{
// (4) Thread A runs Database query synchronously
var cursor = Database.Instance.GetAllObjects();
var status = cursor.MoveToFirst();
while (status == DbStatus.OK)
{
var dbObject= Db.Create(cursor);
// (5) Thread A call OnNext
obs.OnNext(dbObject);
status = cursor.MoveToNext();
}
obs.OnCompleted();
// (12) Thread A finally completes subscription due to Connect()
return Disposable.Empty;
});
}
}
}
我认为问题是一个大缓冲区,一次性将大量结果卸载到ObservableCollection中,为列表框创造了大量工作。
答案 1 :(得分:0)
你的问题在这里:
while (status == DbStatus.OK)
{
var dbObject= Db.Create(cursor);
obs.OnNext(dbObject);
status = cursor.MoveToNext();
}
只要有人订阅,该循环就会以阻止的方式同步运行。由于您在UI线程上创建订阅(在您调用Connect时),它将在UI线程上运行整个事务。将其更改为:
return Observable.Create<DbObject>(obs =>
{
Observable.Start(() => {
var cursor = Database.Instance.GetAllObjects();
var status = cursor.MoveToFirst();
while (status == DbStatus.OK)
{
var dbObject= Db.Create(cursor);
obs.OnNext(dbObject);
status = cursor.MoveToNext();
}
obs.OnCompleted();
}, RxApp.TaskPoolScheduler);
return Disposable.Empty;
});