Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/319.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在填充数据集时更新WPF进度条,全部使用Rx_C#_Wpf_Dataset_Progress Bar_System.reactive - Fatal编程技术网

C# 在填充数据集时更新WPF进度条,全部使用Rx

C# 在填充数据集时更新WPF进度条,全部使用Rx,c#,wpf,dataset,progress-bar,system.reactive,C#,Wpf,Dataset,Progress Bar,System.reactive,我在Rx有点新,所以请原谅我,如果这看起来很愚蠢或明显 我有一个应用程序,它在特定时间扫描选定的文件夹并递归检索所有文件,然后需要将它们存储在数据库中。我希望在这个过程中显示一个进度条,同时保持UI的响应性。一个取消按钮在以后的阶段也很好 我使用Rx实现了这一点,如下所示: // Get a list of all the files var enumeratedFiles = Directory.EnumerateFiles(targetDirectory, "*.*", SearchOpti

我在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);
                    });
但是,在上面的示例中,用户界面被锁定,进程栏在此过程中不会更新。我假设这是因为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解决这个问题


我是不是漏掉了什么明显的东西?如果您有任何建议,我们将不胜感激……

您所写内容的语义非常奇怪:

  • 在UI线程上执行整个文件操作
  • 获取这些项目,然后生成一个新线程
  • 在该线程上,Dispatcher.BeginInvoke
  • 在调用中,添加表行

  • 这似乎不是您真正想要的…

    阅读您的代码看起来您在后台做了很多工作,最后更新了UI。对于此类工作,最好将
    enumeratedFiles
    变量定义为:

    var enumeratedFiles =
        Observable
            .Start(() =>
                Directory
                    .EnumerateFiles(
                        targetDirectory, "*.*", SearchOption.AllDirectories),
                            Scheduler.TaskPool)
            .ObserveOnDispatcher();
    
    您将得到一个相对快速的后台操作,然后是一个UI更新。这是您当前方法的更好实现

    如果您能找到如何更新每个返回文件的UI,请尝试以下可观察的方法:

    var enumeratedFiles =
        Directory
            .EnumerateFiles(targetDirectory, "*.*", SearchOption.AllDirectories)
            .ToObservable(Scheduler.TaskPool)
            .ObserveOnDispatcher();
    
    使用此选项,您肯定需要了解如何为找到的每个文件更新UI


    让我知道其中一个是否对您有效。

    我找到了解决方案,并提供了更多有关发生情况的详细信息:

    在我提到的数据集中插入行时的延迟仅在调试模式下存在,而在不调试的情况下运行时,应用程序不会显示延迟,进度条和项目数的处理速度会快很多倍。我太傻了,没早点测试

    在递归扫描文件时,会有一点延迟(21000个文件会延迟几秒钟),但由于这只发生在您第一次这样做时,我在随后的测试中没有注意到这一点,我只关注对我来说似乎很慢的部分:数据集的填充。我猜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现有文件中使用.takeTill。代码现在如下所示:

    // 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 => ....
    
    //显示取消按钮以允许用户中止进程
    btnCancel.Visibility=可见性.Visibility;
    //将取消单击事件设置为可观察事件,以便我们可以监视它
    var cancelClicked=可观察的。FromEventPattern(btnCancel,“点击”);
    //使用Rx从IEnumerable集合中拾取扫描的文件,将其填充到数据集中,最后将数据集保存到数据库中
    var files=Directory.EnumerateFiles(targetDirectory,“*.*”,SearchOption.AllDirectories)
    .Where(s=>extensions.Contains(System.IO.Path.GetExtension)))
    .ToObservable(TaskPoolScheduler.Default)
    .TakeUntil(单击取消)
    .Do(x=>。。。。
    
    这不是一种实际任务需要几毫秒的情况,但实现进度条会将其变成一个扩展的噩梦?(我没有答案,只是发出嘶嘶声)不,即使我拿出进度条,完成任务的时间与上面的Rx示例中的时间相同。但在该过程中,UI仍然被锁定…:(感谢您的回复。我理解这可能看起来很奇怪,但我已尝试在单独的线程上完成大部分工作。文件枚举不会占用最多的时间,而是填充数据表(在使用此方法结束之前,我已分别测量了这两个任务)。感谢您的回复。我确实测试了您的建议,但问题仍然存在。在我看来,您似乎认为占用最多资源和时间的进程是Directory.enumerated Files,但实际上不是。锁定UI线程并花费最多时间完成的进程是将每个枚举文件插入到ataSet,使用AddRow方法。我用计时器测量了每个步骤,并隔离了每个任务以得出这个结论…@MiDWaN-那么我的第二个选项对您来说是更好的选择,但是您需要更新每个文件的UI。不要一次将所有行添加到
    数据集,一次只添加一行。这样行吗?经过更多的测试后,这似乎有效耽搁了多久