Buffer data from database cursor while keeping UI responsive

前端 未结 2 589
遥遥无期
遥遥无期 2021-01-23 16:59

I have a database catalog that is populated, and a cursor that can be used to retrieve objects. This catalog can obviously be very large, and what I\'d like to do is use

相关标签:
2条回答
  • 2021-01-23 17:11

    Your problem is here:

               while (status == DbStatus.OK)
                {
                    var dbObject= Db.Create(cursor);
                    obs.OnNext(dbObject);
    
                    status = cursor.MoveToNext();
                }
    

    That loop runs synchronously as soon as someone subscribes, in a blocking way. Since you're creating the subscription on the UI thread (at the time you call Connect), it will run the entire thing on the UI thread. Change it to:

    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;
    });
    
    0 讨论(0)
  • 2021-01-23 17:29

    Pending further information, my guess is you've got possibly several zero-length buffers depending on how long the DB query takes, followed by exactly one non-zero length buffer containing all the results. You're probably better off limiting buffer size by length as well as time.

    EDIT - I just wanted to do an analysis of the various threads involved in the original implementation. I don't agree with Paul's analysis, I do not believe the UI Thread is blocked because of the DB query. I believe it's blocked due to large numbers of results being buffered.

    Charlie - please, can you time the DB query in code (not with the debugger) and dump the buffer lengths you are getting too.

    I will annotate the code to show the order of all three threads involved:

    First of all, outside of the provided code, I am assuming a call is made to OnLoad via the Loaded event.

    (1) - UI Thread calls 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;
                });
            }
        }
    }
    

    I think the issue is a large buffer unloading tons of results into the ObservableCollection in one go, creating a ton of work for the listbox.

    0 讨论(0)
提交回复
热议问题