gpt4 book ai didi

c# - 在 MVVM 中,如何防止 BackgroundWorker 卡住 UI?

转载 作者:行者123 更新时间:2023-12-03 10:52:19 25 4
gpt4 key购买 nike

这个问题在这里已经有了答案:





Limit binding updates per second

(3 个回答)


10 个月前关闭。




首先,我在 SO 和网络上看到了许多类似的问题。这些似乎都没有解决我的特定问题。

我有一个简单的BackgroundWorker其工作是逐行读取文件并报告进度以指示文件的进度。文件中总共有 65,553 行,所以对我来说重要的是 BackgroundWorker尽快完成。

由于 MVVM 是基于 separation of concerns (SoC)以及 View 和 View-Model 的解耦,BackgroundWorker更新 View 绑定(bind)到的 View-Model 上的属性。我的设置与 Kent Boorgaart's answer 非常相似关于另一个问题。

在高压力情况下,BackgroundWorker在不休眠的情况下需要大量 CPU,UI 线程处于饥饿状态,无法更新已通过 INotifyPropertyChanged 通知的任何绑定(bind)属性.但是,如果 BackgroundWorker sleep ,那么工作将不会尽快完成。

如何确保 View 在尊重 MVVM 且不限制工作的同时接收进度更新?

在 View 模型中 BackgroundWorker是这样设置的。 Start()函数由 RelayCommand 调用(MVVM-Light 的一部分)。

public void Start(string memoryFile)
{
this.memoryFile = memoryFile;
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += Worker_DoWork;
worker.ProgressChanged += Worker_ProgressChanged;
worker.WorkerReportsProgress = true;
worker.RunWorkerAsync();
}

以下是实际执行工作的代码:
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = (BackgroundWorker)sender;
IsAnalyzing = true;
bw.ReportProgress(0, new ProgressState("Processing..."));

int count = File.ReadLines(memoryFile).Count();
StreamReader reader = new StreamReader(memoryFile);
string line = "";
int lineIndex = 0;
while ((line = reader.ReadLine()) != null)
{
bw.ReportProgress((int)(((double)lineIndex / count) * 100.0d));

//Process record... (assume time consuming operation)
HexRecord record = HexFileUtil.ParseLine(line);

lineIndex++;
if (lineIndex % 150 == 0)
{
//Uncomment to give UI thread some time.
//However, this will throttle the job.
//Thread.Sleep(5);
}
}
bw.ReportProgress(100, new ProgressState("Done."));

Thread.Sleep(1000);
IsAnalyzing = false;
}

private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Progress = e.ProgressPercentage;
if (e.UserState != null)
{
Task = ((ProgressState)e.UserState).Task;
}
}

在上面的代码中,以下属性用于 View 和 View-Model 之间的绑定(bind),每个属性都会触发 INotifyPropertyChanged.PropertyChanged 事件:
  • Progress
  • Task
  • IsAnalyzing


  • 编辑:

    在跟进 Stephen Cleary 和 Filip Cordas 时,我尝试使用 Task.Run()有和没有 ObservableProgress .

    我已经简化了后台任务来迭代数字而不是文件的行。
    private void DoWork(IProgress<ProgressState> progress)
    {
    IsAnalyzing = true;
    progress.Report(new ProgressState(0, "Processing..."));

    for (int i = 0; i < 2000000000; i += 1000000)
    {
    int percent = (int)(((double)i / 2000000000) * 100.0d);
    progress.Report(new ProgressState(percent, String.Format("Processing ({0}%)", percent)));
    Thread.Sleep(5);
    }
    progress.Report(new ProgressState(100, "Done."));

    Thread.Sleep(1000);
    IsAnalyzing = false;
    }

    现在,我以一种或两种方式开始任务(有或没有 ObservableProgress ):
    public void Start(string memoryFile)
    {
    this.memoryFile = memoryFile;

    /* TODO: Uncomment this section to use ObservableProgress instead.
    ObservableProgress.CreateAsync<ProgressState>(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: Comment this section to use ObservableProgress instead.
    var progress = new Progress<ProgressState>();
    progress.ProgressChanged += (s, p) =>
    {
    Progress = p.ProgressPercentage;
    Task = p.Task;
    };
    System.Threading.Tasks.Task.Run(() => DoWork(progress));
    }

    ObservableProgress.cs
    public static class ObservableProgress
    {
    public static IObservable<T> CreateAsync<T>(Func<IProgress<T>, Task> action)
    {
    return Observable.Create<T>(async obs =>
    {
    await action(new Progress<T>(obs.OnNext));
    obs.OnCompleted();

    return Disposable.Empty;
    });
    }
    }

    在这两种情况下(有或没有 ObservableProgress )我发现我仍然需要使用 Thread.Sleep(5) 来限制后台作业.否则 UI 会卡住。

    编辑2:

    我对工作线程内的进度报告进行了小修改:
    for (int i = 0; i < 2000000000; i += 10) //Notice this loop iterates a lot more.
    {
    int percent = (int)(((double)i / 2000000000) * 100.0d);
    //Thread.Sleep(5); //NOT Throttling anymore.
    if (i % 1000000 == 0)
    {
    progress.Report(new ProgressState(percent, String.Format("Processing ({0}%)", percent)));
    }
    }

    通过此修改,UI 不再锁定,更改正在正确传播。为什么会这样?

    最佳答案

    In high-stress scenarios where the BackgroundWorker demands a lot of CPU without sleeping, the UI thread is starved and not able to update any of the bound properties that have been notified via INotifyPropertyChanged. However, if the BackgroundWorker sleeps, then the job will not finish as fast as possible.



    让后台线程使用 CPU 不会干扰 UI 线程。我怀疑实际发生的是后台线程向 UI 线程发送进度更新太快,而 UI 线程根本无法跟上。 (由于 Win32 消息的优先级方式,这最终看起来像一个完整的“卡住”)。

    How can I ensure the View receives progress updates while respecting MVVM and while not throttling the job?



    相当简单:限制进度更新。或者更具体地说,对它们进行采样。

    首先,我推荐使用 Filip 的方法 Task.RunIProgress<T> ;这是 BackgroundWorker 的现代等价物(更多信息 on my blog)。

    其次,为了对进度更新进行采样,您应该使用 IProgress<T> 的实现。这允许您根据时间进行采样(即,不要使用 Progress<T> )。具有基于时间的逻辑的异步序列? Rx 是明确的选择。 Lee Campbell 有一个 great implementation ,我有一个 lesser one .

    例如,使用 Lee Campbell 的 ObservableProgress :
    private void DoWork(IProgress<ProgressState> progress)
    {
    IsAnalyzing = true;
    progress.Report(new ProgressState(0, "Processing..."));

    int count = File.ReadLines(memoryFile).Count();
    StreamReader reader = new StreamReader(memoryFile);
    string line = "";
    int lineIndex = 0;
    while ((line = reader.ReadLine()) != null)
    {
    progress.Report(new ProgressState((int)(((double)lineIndex / count) * 100.0d));

    //Process record... (assume time consuming operation)
    HexRecord record = HexFileUtil.ParseLine(line);

    lineIndex++;
    }
    progress.Report(new ProgressState(100, "Done."));
    IsAnalyzing = false;
    }

    ...

    ObservableProgress.CreateAsync<ProgressState>(progress => Task.Run(() => DoWork(progress)))
    .Sample(TimeSpan.FromMilliseconds(250)) // Update UI every 250ms
    .ObserveOn(this) // Apply progress updates on UI thread
    .Subscribe(p =>
    {
    Progress = p.ProgressPercentage;
    Task = p.Task;
    });

    关于c# - 在 MVVM 中,如何防止 BackgroundWorker 卡住 UI?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40598646/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com