C
C#6mo ago
Cristi

Background Worker giving exceptions.

Hey so I'm working on a File Manager in WPF and I'm trying to use a background worker to calculate file/directory sizes in the background so the application doesn't freeze. It does work but the main problem is that when a directory is getting calculated if the user selects another item while the background worker is running in the background it gives out an exception about how it cannot run multiple instances of itself at the same time so I added logic to cancel the previous background worker if another one is selected, I did everything from setting WorkerSupportsCancellation to true and adding logic to cancel it in the DoWork function and I tried everything from asking ChatGPT to browsing the entire Microsoft documentation about it and tried dozens upon dozens of diffrent solutions but NOTHING worked. At this point I am desperate and any help towards fixing this issue would mean THE WORLD to me. I have linked my MainWindow xaml file and cs file.
45 Replies
Cristi
Cristi6mo ago
Also here's my entire project if you guys need it.
Mayor McCheese
Mayor McCheese6mo ago
I'd suggesting putting your code out in a remote somewhere like github; I don't know you well enough to trust you with a a zip file on discord.
Cristi
Cristi6mo ago
damn ok I'll try to figure that out
Mayor McCheese
Mayor McCheese6mo ago
it all sounds like you're queuing work and not using a background worker so it's likely a bad fit
Cristi
Cristi6mo ago
oh then what should I use? I tried asyncronious stuff with Task's and async and await but that didn't work either
Mayor McCheese
Mayor McCheese6mo ago
so, the challenge becomes, a function of, if I click on Folder A, then Folder B, you want to calculate size for both but what if I click on A, then B, then C, then B again whilst B is still calculating what should happen in that case?
Cristi
Cristi6mo ago
the thing is that, if the user selects another item while one is calculating, I want to just cancel that one and start the new one so it never ends up calculating multiple items at the same time
Mayor McCheese
Mayor McCheese6mo ago
you're also going with WPF as winfoms route, which makes this ... harder; it'll be simpler in mvvm landscape tbh. Are you doing this because it feels easier to you?
Cristi
Cristi6mo ago
I'll try but I started using WPF a few days ago so this is all new teritory for me
Mayor McCheese
Mayor McCheese6mo ago
fair
Cristi
Cristi6mo ago
kinda, also because I don't need the other item's size if the user selected another one
Mayor McCheese
Mayor McCheese6mo ago
You can cancel a backgorund worker but cancelling a background worker is tricky, essentially you have to check if cancellation is pending in your DoWork handler oh you are this is all a big management problem then
Cristi
Cristi6mo ago
yeah kind of
Mayor McCheese
Mayor McCheese6mo ago
I'm thinking
Cristi
Cristi6mo ago
https://github.com/ItsCristi/FileManager okay i manager to put it on git hub
Mayor McCheese
Mayor McCheese6mo ago
is it a private repo?
Cristi
Cristi6mo ago
i dont think so im not sure let me check
Mayor McCheese
Mayor McCheese6mo ago
GitHub
ItsCristi/File-Manager
Contribute to ItsCristi/File-Manager development by creating an account on GitHub.
Mayor McCheese
Mayor McCheese6mo ago
but not FileManager and File-Manager is empty
Cristi
Cristi6mo ago
oh ok https://github.com/ItsCristi/FileManager/releases/tag/v1.0 i think i did it can you acces it sorry it's like the second time im using git as well lol
Mayor McCheese
Mayor McCheese6mo ago
probably you're easiest next step is something like....
private bool _isProcessing = false;

void SizeCalculationWorker_DoWork(object sender, DoWorkEventArgs e)
{
if(_isProcessing) { return; }

_isProcessing = true;
try
{
List<string> selectedItems = e.Argument as List<string>;

if (selectedItems != null)
{
long res = 0;
foreach (string path in selectedItems)
{
if (SizeCalculator.CancellationPending) {
e.Cancel = true;
return;
}

if (File.Exists(path)) res += new FileInfo(path).Length;
else if (Directory.Exists(path)) res += getDirSize(path);
}

e.Result = getReadableSize(res);
}
}
finally
{
_isProcessing = false;
}
}
private bool _isProcessing = false;

void SizeCalculationWorker_DoWork(object sender, DoWorkEventArgs e)
{
if(_isProcessing) { return; }

_isProcessing = true;
try
{
List<string> selectedItems = e.Argument as List<string>;

if (selectedItems != null)
{
long res = 0;
foreach (string path in selectedItems)
{
if (SizeCalculator.CancellationPending) {
e.Cancel = true;
return;
}

if (File.Exists(path)) res += new FileInfo(path).Length;
else if (Directory.Exists(path)) res += getDirSize(path);
}

e.Result = getReadableSize(res);
}
}
finally
{
_isProcessing = false;
}
}
that should bounce requests until either processing is done, or cancelled. but that doesn't help your UX
Cristi
Cristi6mo ago
oh okay I'll try it, I'm sorry but right now I have to go to sleep so I really gotta go thank you so much though you have no idea how much it means to me
Mayor McCheese
Mayor McCheese6mo ago
You can make the _isProcessing a public property like public bool IsProcessing {get;set;} and set some button press-ability to enabled or disabled. I'd have to play with that a bit in WPF, it might have to be a dependecy property.
Sir Rufo
Sir Rufo6mo ago
It is very uncommon to use the very old BackgroundWorker instead of async/await and IProgress<T>.
Mayor McCheese
Mayor McCheese6mo ago
yeah I'm really not a fan of background worker I'd change a lot on the whole approach tbh.
Cristi
Cristi6mo ago
@🧨 New Years Mayor McCheese 🧨 I'm sorry for the ping but I added the async and Task logic but it still doesn't work, my application freezes after I select an item until it's done calculating the size, what did I do wrong? The main logic is here string getReadableSize(long sib) { string[] sizeSuffixes = { "B", "KB", "MB", "GB", "TB", "PB" }; int i = 0; double size = sib; while (size >= 1024 && i < sizeSuffixes.Length - 1) { size /= 1024; i++; } return $"{size:0.##} {sizeSuffixes[i]}"; } string getSelectedItemExtension() { foreach (StackPanel item in FileList.SelectedItems) { if (item.Children[1] is Label label) { string path = Path.Combine(CurrentPath, label.Content.ToString()); if(File.Exists(path)) return new FileInfo(path).Extension; else if(Directory.Exists(path)) return "Folder"; } } return null; } async Task<long> getDirSize(string path) { long res = 0; try { foreach (string file in Directory.GetFiles(path)) res += new FileInfo(file).Length; foreach (string dir in Directory.GetDirectories(path)) res += await getDirSize(dir); } catch{} return res; } async Task<string> getSelectionSize() { long res = 0; foreach(StackPanel item in FileList.SelectedItems) { if (item.Children[1] is Label label) { string path = Path.Combine(CurrentPath,label.Content.ToString()); if(Directory.Exists(path)) res += await getDirSize(path); else if(File.Exists(path)) res += new FileInfo(path).Length; } } return getReadableSize(res); } void FileList_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (FileList.SelectedItems.Count == 0) { CurrentItemSize = "Size: --"; CurrentItemType = "Type: --"; } else { CurrentItemSize = "Size: Calculating..."; CurrentItemSize = getSelectionSize().Result; if (FileList.SelectedItems.Count == 1) CurrentItemType = $"Type: {getSelectedItemExtension()}"; else CurrentItemType = "Type: --"; } } it should work but it doesn't and I have no clue why
Sir Rufo
Sir Rufo6mo ago
The keyword async does not guarantee that your method runs asynchron. Your code is synchron all the way and that is the reason why it is only blocking. getSelectionSize().Result; is the perfect dead-lock pattern. Even if your code were perfect asynchron this call will block or dead-lock your app anyway.
Cristi
Cristi6mo ago
i changed it again and made the FileList_SelectionChanged function async and instead of getSelectionSize().Result I did await getSelectionSize() and it still didn't work
Mayor McCheese
Mayor McCheese6mo ago
I'll check in a bit, I've got to wrap up some eoy stuff at work.
Cristi
Cristi6mo ago
It's ok thank you a lot
Sir Rufo
Sir Rufo6mo ago
Your async methods are all running synchron so it is normal that your code will block.
Cristi
Cristi6mo ago
but why? how can I make them asyncronious?? like I seriously tried everything if you could help me id mean the world to me at this point I have tried everything and nothing works
Sir Rufo
Sir Rufo6mo ago
if you have nothing to await, then build a normal (sync) method and when you need an async result call it with var result = await Task.Run( () => CalulateWithSyncMethod() ); But your method is somehow difficult, because you access UI components within the calculation (StackPanel, Label) That is one reason for MVVM pattern.
Mayor McCheese
Mayor McCheese6mo ago
problem spaces in this arena are much easier with mvvm
Sir Rufo
Sir Rufo6mo ago
With MVVM you will never face problems like this, but other 😁
Mayor McCheese
Mayor McCheese6mo ago
background worker is a dated concept, it's more applicable to winforms as a managed component. ( opinion )
Sir Rufo
Sir Rufo6mo ago
The OP has already switched to the async/await pattern
Mayor McCheese
Mayor McCheese6mo ago
ah, I didn't get a chance to look back yet it's also more complex because of cancellation and such
jcotton42
jcotton426mo ago
There's no reason to use it in Forms either BW has been completely supplanted by Task
Mayor McCheese
Mayor McCheese6mo ago
It's a component that you can ( probably should not ) add to a form.
jcotton42
jcotton426mo ago
Still not a reason to use it
Mayor McCheese
Mayor McCheese6mo ago
Sure, but in winforms people are going to, because 1. they don't know better, 2. it's accessible in the tool window.
Cristi
Cristi6mo ago
omg i finally managed to do it this line of code saved my god damn live you have no idea how fricking thankful I am I finally managed to get it working exacly how I wanted it Thank you all SO, SOOOO much! ❤️
Mayor McCheese
Mayor McCheese6mo ago
@Cristi I'd be careful with Task.Run generally; it's okay here, because it's a WPF app; but you can run into some serious problems in other types of apps. https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-using.html
Task.Run Etiquette Examples: Don't Use Task.Run for the Wrong Thing
I had quite a few comments on my last post asking for more explicit examples of Correct vs. Incorrect Task.Run usage.
Cristi
Cristi6mo ago
Okay thank you a lot <3