填充DataSet时更新WPF进度条,全部使用Rx [英] Update WPF progress bar while filling DataSet, all using Rx

查看:33
本文介绍了填充DataSet时更新WPF进度条,全部使用Rx的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对 Rx 有点陌生,所以如果这看起来很傻或很明显,请原谅...

I'm a bit new in Rx, so please excuse me if this seems silly or obvious...

我有一个应用程序,它在某个时间扫描选定的文件夹并递归检索所有文件,之后它需要将它们存储在数据库中.我想在该过程中显示一个进度条,同时保持 UI 响应当然.取消按钮在稍后阶段也会很好.

I have an application which at a certain time, is meant to scan a selected folder and retrieve all files recursively, after which it needs to store them in a database. I would like to display a progress bar during that process while keeping the UI responsive of course. A cancel button would also be nice at a later stage.

我已经使用 Rx 实现了这一点,如下所示:

I've implemented this using Rx, like so:

// Get a list of all the files
var enumeratedFiles = Directory.EnumerateFiles(targetDirectory, "*.*", SearchOption.AllDirectories);

// prepare the progress bar
            double value = 0;
            progBar.Minimum = 0;
            progBar.Maximum = enumeratedFiles.Count();
            progBar.Value = value;
            progBar.Height = 15;
            progBar.Width = 100;
            statusBar.Items.Add(progBar);

var files = enumeratedFiles.ToObservable()
                .SubscribeOn(TaskPoolScheduler.Default)
                .ObserveOnDispatcher()
                .Subscribe(x =>
                    {
                        myDataSet.myTable.AddTableRow(System.IO.Path.GetFileNameWithoutExtension(x));
                        value++;
                    },
                    () =>
                    {
                        myDataSetTableAdapter.Update(myDataSet.myTable);
                        myDataSetTableAdapter.Fill(myDataSet.myTable);
                        statusBar.Items.Remove(progBar);
                    });

但是,对于上面的示例,UI 被锁定,并且在此过程中进度条不会更新.我认为这是因为 AddTableRow 方法阻塞了线程,尽管我认为 SubscribeOn(TaskPoolScheduler) 应该在新线程上运行任务?

However, with the above example the UI is locked and the progress bar doesn't update during the process. I assume that's because the AddTableRow method is blocking the thread, although I believed that the SubscribeOn(TaskPoolScheduler) was supposed to run the task on a new thread?

我也尝试了几种不同的方法,结果各不相同.例如添加一个 .Do 行:

I've tried a few different approaches as well, with varying results. For example adding a .Do line:

var files = enumeratedFiles.ToObservable()
                .Do(x => myDataSet.myTable.AddTableRow(System.IO.Path.GetFileNameWithoutExtension(x)))
                .SubscribeOn(TaskPoolScheduler.Default)
                .ObserveOnDispatcher()
                .Subscribe(x =>
                    {
                        value++;
                    },
                    () =>
                    {
                        myDataSetTableAdapter.Update(myDataSet.myTable);
                        myDataSetTableAdapter.Fill(myDataSet.myTable);
                        statusBar.Items.Remove(progBar);
                        btnCancel.Visibility = Visibility.Collapsed;
                    });

这实际上显示了进度条的更新,并且UI没有完全锁定而是波涛汹涌,性能下降......

this actually shows the progress bar updates, and the UI is not completely locked but it is choppy and the performance crawls down...

我尝试使用 BackgroundWorker 来完成相同的工作,但性能比上面的 Rx 方法差得多(例如,对于 21000 个文件,Rx 方法需要几秒钟,而 BackgroundWorker 需要几分钟才能完成).

I've tried using a BackgroundWorker to do the same job, but the performance is way worse than the Rx approach above (for example, for 21000 files the Rx approach takes a few seconds while the BackgroundWorker a few minutes to finish).

我也看到过使用 Delegates 处理进度条的 ValueProperty 方法的类似问题,但如果可能的话,我真的很想使用 Rx 来解决这个问题.

I've also seen similar problems being tackled with Delegates for the progress bar's ValueProperty method, but I would really like to solve this using Rx if possible.

我在这里遗漏了一些明显的东西吗?任何建议将不胜感激...

Am I missing something obvious here? Any suggestion would be highly appreciated...

推荐答案

我已经找到了解决方案,以及更多关于发生了什么的细节:

I've found the solution, and a bit more details on what's going on:

在我提到的 DataSet 中插入行时的延迟仅在 Debug 模式下存在,而在没有 Debug 的情况下运行时,应用程序不会显示延迟,并且进度条和项目数的处理速度要快很多倍.愚蠢的我没有早点测试......

The delay while inserting the rows in the DataSet I mentioned is only there in Debug mode, whereas when run without Debug the application doesn't show that delay and the progress bar and number of items are processed many times faster. Silly me for not testing that earlier...

在递归扫描文件时会有一点延迟(对于 21000 个文件有几秒钟的延迟),但由于它只在您第一次这样做时发生,所以我在随后的测试中没有注意到它,我只关注对我来说似乎很慢的部分:数据集的填充.我猜 Directory.EnumerateFiles 会将所有内容缓存在内存中,因此任何其他读取相同文件的尝试都会立即完成?

While scanning for the files recursively there is a slight delay (a few seconds for 21000 files) but since it only happens the first time you do that, I failed to notice it in my subsequent tests and I was only focusing on the part that seemed slow to me: the filling of the DataSet. I guess that Directory.EnumerateFiles caches everything in memory so any other attempt to read the same files completes instantly?

此外,似乎不需要 myDataSetTableAdapter.Fill(myDataSet.myTable) 行,因为 .Update 方法已经将内容保存在数据库本身中.

Also, it seems that the myDataSetTableAdapter.Fill(myDataSet.myTable) line is not needed, since the .Update method already saves the contents in the database itself.

对我有用的最终代码片段如下:

The final code snippet that worked for me is the following:

progBar.Height = 15;
progBar.Width = 100;
progBar.IsIndeterminate = true;
statusBar.Items.Add(progBar);

var files = Directory.EnumerateFiles(targetDirectory, "*.*", SearchOption.AllDirectories)
            .Where(s => extensions.Contains(System.IO.Path.GetExtension(s))) // "extensions" has been specified further above in the code
            .ToObservable(TaskPoolScheduler.Default)
            .Do(x => myDataSet.myTable.AddTableRow(System.IO.Path.GetFileNameWithoutExtension(x), x, "default")) // my table has 3 columns
            .TakeLast(1)
            .Do(_ => myDataSetmyTableTableAdapter.Update(myDataSet.myTable))
            .ObserveOnDispatcher()
            .Subscribe(xy =>
                {
                    //progBar.Value++; //commented out since I've switched to a marquee progress bar
                },
                () =>
                {
                    statusBar.Items.Remove(progBar);
                    btnCancel.Visibility = Visibility.Collapsed;
                });

这对我来说似乎工作得很好,感谢所有帮助家伙!

This seems to work fine for me, thanks for all the help guys!

我进一步扩展了上述内容,包括取消按钮功能.如果用户单击取消"按钮,则该过程立即停止.我试图让它尽可能优雅,所以我从取消按钮的 Click 事件中添加了一个 Observable,然后在上面的现有文件 Observable 中使用了 .TakeUntil.现在的代码如下所示:

edit: I've further expanded the above, to include a Cancel button functionality. If the user clicks on the Cancel button, the process is stopped immediately. I've tried to keep it as elegant as possible, so I've added an Observable from the Click event of the Cancel button, then used .TakeUntil in my existing files Observable above. The code now looks like this:

// Show the Cancel button to allow the user to abort the process
btnCancel.Visibility = Visibility.Visible;

// Set the Cancel click event as an observable so we can monitor it
var cancelClicked = Observable.FromEventPattern<EventArgs>(btnCancel, "Click");

// Use Rx to pick the scanned files from the IEnumerable collection, fill them in the DataSet and finally save the DataSet in the DB
var files = Directory.EnumerateFiles(targetDirectory, "*.*", SearchOption.AllDirectories)
            .Where(s => extensions.Contains(System.IO.Path.GetExtension(s)))
            .ToObservable(TaskPoolScheduler.Default)
            .TakeUntil(cancelClicked)
            .Do(x => ....

这篇关于填充DataSet时更新WPF进度条,全部使用Rx的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆