C
C#2y ago
edgarka_

✅ BackgroundWorker in WPF MVVM

Hi! I'm trying to get progressbar and text to update while performing a lengthy task. I was wondering if it is possible to do so or should I create a thread for "PerformTask" file separately? I tried to play around with BackgroundWorker, and it updates "CurrentProgress" variable, but UI updates only after execution. GitHub: https://github.com/EdgarKa/AsyncProgressDemo Thank you 🙂 Edit: I dropped GitHub repo so it would be easier to see and/or run as due to files amount it would be lengthy
GitHub
GitHub - EdgarKa/AsyncProgressDemo
Contribute to EdgarKa/AsyncProgressDemo development by creating an account on GitHub.
8 Replies
hengi
hengi2y ago
BackgroundWorkers are a bit old, but they still work it's a bit hard to unpack, so: in your Method InitializeBackgroundWorker() you are setting the EventHandler DoWork. This is actually the thing that will run when you do bw.RunWorkerAsync() The Progress you report will need to be reported inside the DoWork EventHandler. ProgressChanged then runs on the UI thread, where you can update your UI as necessary. You can report Progress by calling ReportProgress. Also, Cancellation needs to be handled in DoWork, if you want to support it. You can check whether a BackgroundWorker was cancelled by just checking bw.CancellationPending. Once your BackgroundWorker completes, RunWorkerCompleted is called. This will also run on the UI thread. working example would be:
public void InitializeBackgroundWorker()
{
bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_RunWorker_DoWork);
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation= true;
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
}

private void bw_RunWorker_DoWork(object? sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
int i = 0;
while (i < 100)
{
Thread.Sleep(100);
i += 10;
bw.ReportProgress(i);
}
}

private void bw_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e)
{
CurrentProgress = 100;
ProgressText = "Completed";
MessageBox.Show("Completed");
}

private void bw_ProgressChanged(object? sender, ProgressChangedEventArgs e)
{
ProgressText = "In progress";
CurrentProgress = e.ProgressPercentage;
}
public void InitializeBackgroundWorker()
{
bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_RunWorker_DoWork);
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation= true;
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
}

private void bw_RunWorker_DoWork(object? sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
int i = 0;
while (i < 100)
{
Thread.Sleep(100);
i += 10;
bw.ReportProgress(i);
}
}

private void bw_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e)
{
CurrentProgress = 100;
ProgressText = "Completed";
MessageBox.Show("Completed");
}

private void bw_ProgressChanged(object? sender, ProgressChangedEventArgs e)
{
ProgressText = "In progress";
CurrentProgress = e.ProgressPercentage;
}
(cancellation would be implemented by checking bw.CancellationPending in the while loop for example) Stephen Cleary made a good series of articles on backgroundworkers vs Tasks, you can check it out here https://blog.stephencleary.com/2013/05/taskrun-vs-backgroundworker-round-1.html It explains the basics of both and compares them
jcotton42
jcotton422y ago
Yeah, definitely use Task.Run massively simpler
hengi
hengi2y ago
yes ofc, most of the stuff you do in WPF usually does not require even that, Task.Run is only really needed when you do CPU heavy stuff. Everything else is awaitable at least somewhere so you can just use async Events or expand your Command class to support Async Commands and stuff
edgarka_
edgarka_OP2y ago
Thank you for your help @hengi! Issue is, it still makes UI change after "PerformTask" is executed. Is there a way to update UI between fist two "for loops" (for example move progress bar to 10) or it would require separating UI and command with thread?
hengi
hengi2y ago
oh yeah, you should not manipulate the background worker outside the Events if you want to use the PerformTask class, you have to do it like this: MainPageViewModel.cs
public void InitializeBackgroundWorker()
{
bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_RunWorker_DoWork);
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
}

private void bw_RunWorker_DoWork(object? sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
PerformTask.InitializeTask(this, bw);
}

private void bw_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e)
{
CurrentProgress = 100;
ProgressText = "Completed";
MessageBox.Show("Completed");
}

private void bw_ProgressChanged(object? sender, ProgressChangedEventArgs e)
{
ProgressText = "In progress";
CurrentProgress = e.ProgressPercentage;
}
public void InitializeBackgroundWorker()
{
bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_RunWorker_DoWork);
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
}

private void bw_RunWorker_DoWork(object? sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
PerformTask.InitializeTask(this, bw);
}

private void bw_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e)
{
CurrentProgress = 100;
ProgressText = "Completed";
MessageBox.Show("Completed");
}

private void bw_ProgressChanged(object? sender, ProgressChangedEventArgs e)
{
ProgressText = "In progress";
CurrentProgress = e.ProgressPercentage;
}
PerformTask.cs
public static class PerformTask
{
public static void InitializeTask(MainPageViewModel _mainPageViewModel, BackgroundWorker _bw)
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(100);
//
}
_bw.ReportProgress(10);


for (int i = 0; i < 3; i++)
{
Thread.Sleep(1000);
//
}
_bw.ReportProgress(20);

for (int i = 0; i < 3; i++)
{
Thread.Sleep(1000);
}
}
}
public static class PerformTask
{
public static void InitializeTask(MainPageViewModel _mainPageViewModel, BackgroundWorker _bw)
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(100);
//
}
_bw.ReportProgress(10);


for (int i = 0; i < 3; i++)
{
Thread.Sleep(1000);
//
}
_bw.ReportProgress(20);

for (int i = 0; i < 3; i++)
{
Thread.Sleep(1000);
}
}
}
ExecuteCommand.cs
public class ExecuteCommand : CommandBase
{
private readonly MainPageViewModel _mainPageViewModel;
BackgroundWorker _bw;

public ExecuteCommand(MainPageViewModel mainPageViewModel, BackgroundWorker bw)
{
_mainPageViewModel = mainPageViewModel;
_bw = bw;
}

public event EventHandler CanExexuteChanged;

public bool CanExecute(object parameter)
{
return true;
}

public override void Execute(object parameter)
{
_bw.RunWorkerAsync();
}
}
public class ExecuteCommand : CommandBase
{
private readonly MainPageViewModel _mainPageViewModel;
BackgroundWorker _bw;

public ExecuteCommand(MainPageViewModel mainPageViewModel, BackgroundWorker bw)
{
_mainPageViewModel = mainPageViewModel;
_bw = bw;
}

public event EventHandler CanExexuteChanged;

public bool CanExecute(object parameter)
{
return true;
}

public override void Execute(object parameter)
{
_bw.RunWorkerAsync();
}
}
BackgroundWorkers are just a bit old and they feel a bit like Java Threads? Where you subclass a Thread but with Events instead. Just treat the Events like enclosed actions, do not manipulate the BackgroundWorker outside of them except for Cancellation
edgarka_
edgarka_OP2y ago
This is exactly what I needed! Thanks a lot! Didn't think it'd work on Thread.Sleep I am also working with older version of .NET (where my main project is) so it might be as good to use BackgroundWorker. At the same time I'll be looking into Threads and Tasks
hengi
hengi2y ago
BackgroundWorkers take one ThreadPool Thread, Thread.Sleep sleeps the current Thread ^^
Accord
Accord2y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server