当前位置: 首页 > 编程语言 > C#

MVVM中,如何防止BackgroundWorker卡死UI?分享

时间:2023-04-10 16:54:24 C#

MVVM中,如何防止BackgroundWorker卡死UI?首先,我在SO和网络上看到了很多类似的问题。这些似乎都不能解决我的特定问题。我有一个简单的BackgroundWorker,它的工作是逐行读取文件并报告进度以指示它有多远。文件中共有65,553行,因此BackgroundWorker尽快完成对我来说很重要。由于MVVM是建立在关注点分离(SoC)和视图与视图模型分离的基础上的,因此BackgroundWorker会更新视图绑定到的视图模型上的属性。我的设置与KentBoorgaart在另一个问题上的回答非常相似。在高压力情况下,BackgroundWorker在没有休眠的情况下需要大量CPU,UI线程处于饥饿状态并且无法更新已通过INotifyPropertyChanged通知的任何绑定属性。但是,如果BackgroundWorker休眠,作业将不会很快完成。如何确保View在尊重MVVM的同时接收进度更新,同时不限制工作?在View-Model中,BackgroundWorker设置如下。Start()函数由RelayCommand调用(MVVM-LightStart()的一部分。publicvoidStart(stringmemoryFile){this.memoryFile=memoryFile;BackgroundWorkerworker=newBackgroundWorker();worker.DoWork+=Worker_DoWork;worker.ProgressChanged+=Worker_ProgressChanged;worker.WorkerReportsProgress=true;worker.RunWorkerAsync();}下面是实际工作的代码:privatevoidWorker_DoWork(objectsender,DoWorkEventArgse){BackgroundWorkerbw=(BackgroundWorker)sender;IsAnalyzing=真;bw.ReportProgress(0,newProgressState("Processing..."));intcount=File.ReadLines(memoryFile).Count();StreamReaderreader=newStreamReader(memoryFile);字符串行="";int行索引=0;while((line=reader.ReadLine())!=null){bw.ReportProgress((int)(((double)lineIndex/count)*100.0d));//处理记录...(假设耗时操作)HexRecordrecord=HexFileUtil.ParseLine(line);行索引++;if(lineIndex%150==0){//取消注释给UI线程一些时间。//但是,这会限制工作。//线程.睡眠(5);}}bw.ReportProgress(100,newProgressState("Done."));线程.睡眠(1000);IsAnalyzing=false;}privatevoidWorker_ProgressChanged(objectsender,ProgressChangedEventArgse){Progress=e.ProgressPercentage;if(e.UserState!=null){Task=((ProgressState)e.UserState).Task;在上面的代码中,以下属性用于View和View-Model之间的绑定,每个属性都会触发INotifyPropertyChanged.PropertyChanged事件:编辑:在StephenCleary和FilipTask.Run()的后续行动中,我尝试了在有和没有ObservableProgress的情况下使用Task.Run()我已经简化了后台任务以迭代数字而不是文件行。privatevoidDoWork(IProgressprogress){IsAnalyzing=true;progress.Report(newProgressState(0,"处理中..."));对于(inti=0;i<2000000000;i+=1000000){intpercent=(int)(((double)i/2000000000)*100.0d);progress.Report(newProgressState(percent,String.Format("Processing({0}%)",percent)));线程.睡眠(5);}progress.Report(newProgressState(100,"Done."));线程.睡眠(1000);IsAnalyzing=false;现在,我用一种或两种方式启动任务(使用或不使用ObservableProgress):publicvoidStart(stringmemoryFile){this.memoryFile=memoryFile;/*TODO:取消注释此部分以改用ObservableProgress。ObservableProgress.CreateAsync(progress=>System.Threading.Tasks.Task.Run(()=>DoWork(progress))).Sample(TimeSpan.FromMilliseconds(50)).ObserveOn(Application.Current.Dispatcher).Subscribe(p=>{Progress=p.ProgressPercentage;Task=p.Task;});*///TODO:评论此部分以改用ObservableProgress。varprogress=newProgress();progress.ProgressChanged+=(s,p)=>{Progress=p.ProgressPercentage;Task=p.Task;};System.Threading.Tasks.Task.Run(()=>DoWork(进度));}ObservableProgress.cspublicstaticclassObservableProgress{publicstaticIObservableCreateAsync(Funcaction){returnObservable.Create(asyncobs=>{awaitaction(newProgress(obs.OnNext));obs.OnCompleted();返回Disposable.Empty;});在这两种情况下(有或没有ObservableProgress)我发现我仍然需要使用Thread.Sleep(5)来限制后台作业,否则UI会冻结。编辑2:我对工作线程内的报告做了一个小修改:for(inti=0;i<2000000000;i+=10)//注意这个循环迭代了很多。{int百分比=(int)(((double)i/2000000000)*100.0d);//线程.睡眠(5);//不再节流。if(i%1000000==0){progress.Report(newProgressState(percent,String.Format("Processing({0}%)",percent)));}}通过此修改,UI不再被锁定并且更改正常传播。为什么?在高压力情况下,BackgroundWorker在没有休眠的情况下需要大量CPU,UI线程处于饥饿状态并且无法更新已通过INotifyPropertyChanged通知的任何绑定属性。但是,如果BackgroundWorker休眠,作业将不会很快完成。让后台线程使用CPU不会干扰UI线程。我怀疑实际发生的情况是后台线程向UI线程发送进度更新的速度太快,而UI线程根本跟不上。(由于Win32消息的优先级排序方式,这最终看起来像是完全“冻结”)。如何确保View在尊重MVVM的同时接收进度更新,同时不限制工作?很简单:限制进度更新。或者更具体地说,运行它们。首先,我建议将Filip的Task.Run方法与IProgress一起使用;这是BackgroundWorker的现代版本(更多信息在我的博客上)。其次,为了对进度更新进行采样,您应该使用IProgress的实现,它允许您根据时间进行采样(即,不使用Progress)。具有基于时间的逻辑的异步序列?Rx是一个明智的选择。LeeCampbell有一个很好的实现,而我有一个较小的实现。例如,使用LeeCampbell的ObservableProgress:privatevoidDoWork(IProgressprogress){IsAnalyzing=true;progress.Report(newProgressState(0,"处理中..."));intcount=File.ReadLines(memoryFile).Count();StreamReaderreader=newStreamReader(memoryFile);字符串行="";int行索引=0;while((line=reader.ReadLine())!=null){progress.Report(newProgressState((int)(((double)lineIndex/count)*100.0d));//处理记录...(假设耗时操作)HexRecordrecord=HexFileUtil.ParseLine(line);lineIndex++;}progress.Report(newProgressState(100,"Done."));IsAnalyzing=false;}...ObservableProgress.CreateAsync(progress=>Task.Run(()=>DoWork(progress))).Sample(TimeSpan.FromMilliseconds(250))//每250毫秒更新一次UI.ObserveOn(this)//在UI线程上应用进度更新.Subscribe(p=>{Progress=p.ProgressPercentage;Task=p.Task;});为什么要使用BackgroundWorker?这是一个带有任务的简单进度实现,如果访问PropertyChanged调用,它不会阻塞UI线程Do=newGalaSoft.MvvmLight.Command.RelayCommand(()=>{varprogress=newProgress();progress.ProgressChanged+=(s,p)=>Progress=p;//运行并忘记DoWork(progress);});publicasyncTaskDoWork(IProgressprogress=null){awaitTask.Run(()=>{for(inti=1;i有关此主题的更多信息https://blogs.msdn.microsoft.com/dotnet/2012/06/06/async-in-4-5-enabling-progress-and-cancellation-in-async-apis/forasynchronousprogramming以上是C#学习教程:在MVVM中,如何防止BackgroundWorker卡死UI?所有的分享的内容,如果对你有用,需要了解更多C#学习教程,希望大家多多关注。本文收集自网络,不代表立场,如涉及侵权,敬请谅解点击右侧联系管理员删除,如需转载请注明出处: