我在Rx中有点新,所以如果这看起来很愚蠢或明显,请原谅我......
我有一个应用程序,它在某个时间,用于扫描选定的文件夹并递归检索所有文件,之后需要将它们存储在数据库中。我希望在该过程中显示进度条,同时保持UI响应当然。取消按钮在稍后阶段也会很好。
我已经使用Rx实现了这一点,如下所示:
// 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)应该在新线程上运行任务?
我也尝试了一些不同的方法,结果各不相同。例如,添加.Do行:
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没有完全锁定,但它不稳定且性能下降......
我已尝试使用BackgroundWorker执行相同的工作,但性能比上面的Rx方法更差(例如,对于21000个文件,Rx方法需要几秒钟,而BackgroundWorker需要几分钟才能完成)
我也看到了代理人为进度条的ValueProperty方法解决了类似的问题,但如果可能的话,我真的想用Rx解决这个问题。
我错过了一些明显的东西吗?任何建议都将受到高度赞赏......
答案 0 :(得分:1)
你写的东西的语义很奇怪:
这看起来不像你真正想要的......
答案 1 :(得分:1)
我找到了解决方案,以及有关正在发生的事情的更多细节:
在我提到的DataSet中插入行时的延迟仅在调试模式下,而在没有调试的情况下运行时,应用程序不会显示延迟,并且进度条和项目数的处理速度要快许多倍。傻傻的我没有早点测试......
在递归扫描文件时会有一点延迟(对于21000个文件只有几秒钟),但由于它只是在你第一次这样做时才会发生,我在后来的测试中没有注意到它,而我只是专注于对我来说似乎很慢的部分:填充DataSet。我猜Directory.EnumerateFiles缓存内存中的所有内容,以便任何其他尝试读取相同文件立即完成?
此外,似乎不需要myDataSetTableAdapter.Fill(myDataSet.myTable)行,因为.Update方法已经将内容保存在数据库本身中。
对我有用的最终代码段如下:
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;
});
这对我来说似乎很好,感谢所有帮助人员!
编辑:我进一步扩展了上面的内容,包括一个取消按钮功能。如果用户单击“取消”按钮,则会立即停止该过程。我试图让它尽可能优雅,所以我从Cancel按钮的Click事件中添加了一个Observable,然后在我上面的Observable现有文件中使用.TakeUntil。代码现在看起来像这样:
// 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 => ....
答案 2 :(得分:0)
阅读你的代码似乎你在后台做了很多工作,然后最后更新了UI。对于此类工作,最好将enumeratedFiles
变量定义为:
var enumeratedFiles =
Observable
.Start(() =>
Directory
.EnumerateFiles(
targetDirectory, "*.*", SearchOption.AllDirectories),
Scheduler.TaskPool)
.ObserveOnDispatcher();
您将获得相对较快的后台操作,然后进行单个UI更新。这是您当前方法的更好实现。
如果你能弄清楚如何更新返回的每个文件的UI,那么试试这个observable:
var enumeratedFiles =
Directory
.EnumerateFiles(targetDirectory, "*.*", SearchOption.AllDirectories)
.ToObservable(Scheduler.TaskPool)
.ObserveOnDispatcher();
使用此选项,您肯定需要弄清楚如何更新找到的每个文件的UI。
让我知道是否适合你。