-1

I'm currently learning C#, implementing a project with Visual Studio Community. I want it clean and appropriately organized from the start, so, as a general rule, I try not to mix the UI code with the underlying logic; also, I'd like specific parts the code to be used across the whole project.

Hence I made a class populationIdParserWorker in a separate file than the respective UI element, PopulationIdParser. It worked just fine, until I came up with an idea to add a progress bar to the UI. I learned that to make it actually show the progress, I can use BackgroundWorker to introduce the necessary multi-threading. I've been doing some research on how to use it, but I only found such examples, where the actual logic was done inside the UI code, which doesn't seem appropriate to me in terms of bigger projects. So I came up with the idea to pass my worker_DoWork method's arguments to the actual logic, located in populationIdParserWorker class' method process_file_lite. Is it a good practice or should it be done another way? How else can I do it?

Side note: The thing isn't working quite the way it is supposed to, because the progress bar is reaching some point and then the UI holds for a moment, only for the bar to jump to the end immediately. I'm not sure if it has something to do with my method of passing the arguments as a reference - I'm going to check it soon. I'm not convinced yet, because the bar freezes just after all true work has been done, i.e. the remaining lines of the file are meaningless, so they are being read and skipped.

Here are the pieces of code:

From PopulationIdParser.xaml.cs:

private void GenerateFiles_Click(object sender, RoutedEventArgs e)
        {
            ParsingStatus.Value = 0;
            parsingStatusBarPassing.set_all(Convert.ToInt32(ParsingStatus.Minimum), Convert.ToInt32(ParsingStatus.Maximum),
                new IntWithPercentScale(ParsingStatus.Value, ParsingStatus.Maximum));
            parserPathsArgs.set_all(parsingStatusBarPassing,
                SaveBrowser.Text, UsedBrowser.Text, FreeBrowser.Text, default_profs_name);

            BackgroundWorker worker = new BackgroundWorker();
            worker.WorkerReportsProgress = true;
            worker.DoWork += worker_DoWork;
            worker.ProgressChanged += worker_ProgressChanged;
            worker.RunWorkerCompleted += worker_RunWorkerCompleted;
            worker.RunWorkerAsync(parserPathsArgs);
        }
        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            populationIdParserWorker.process_file_lite(ref sender, ref e);
        }
        void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            ParsingStatus.Value = e.ProgressPercentage;
            if(e.UserState != null)
            {
                ParsingPercentage.Text = (e.UserState).ToString() + "%";
            }
        }
        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            MessageBox.Show("Done.");
        }

From PopulationIdParserWorker.cs:

public void process_file_lite(ref object sender, ref DoWorkEventArgs e)
        {
        ///A file is opened here, some pre-processing is done
            while ((line = file.ReadLine()) != null)
                {
                    ///Parsing is done here
                    parsingProgress = Convert.ToInt32(((double)i / flen) * parsingStatusBar.maximum);
                    parsingProgressPercentage = Convert.ToInt32(parsingProgress / parsingStatusBar.value.get_scale());
                    (sender as BackgroundWorker).ReportProgress(parsingProgress, parsingProgressPercentage);
                    i++;
                }
        }

In PopulationIdParser.xaml.cs: ParsingStatus is the progress bar; parsingStatusBarPassing and parserPathsArgs are utility objects used to store the data to be passed between different classes, UIs, threads etc.

In PopulationIdParserWorker.cs: 'flen' is the lines count of the processed file and parsingStatusBar is an object to store information about the progress bar showing the parsing status.

J S
  • 9
  • 3

1 Answers1

0

In general, BackgroundWorker is a pretty old and complicated thing. I suggest Task and async/await approach. And maybe IProgress interface + Progress class for callbacks reporting progress.

Let's assume that ParsingStatus is ProgressBar and ParsingPercentage is TextBlock.

IProgress<int> Percentage; // progress callback

// pay attention to 'async' here
private async void GenerateFiles_Click(object sender, RoutedEventArgs e)
{
    Percentage = new Progress<int>(ReportProgress); // assign callback, you may move this to constructor

    int progress = 0;
    int maximum = (int)ParsingStatus.Maximum;
    Percentage?.Report(progress); // report progress

    FileStream fs = File.OpenRead("file.txt");
    long position = 0;
    long length = fs.Length;
    using (StreamReader sr = new StreamReader(fs))
    {
        while (!sr.EndOfStream)
        {
            string line = await sr.ReadLineAsync(); // read file asynchronously
            string result = await ParseLine(line); // call parser
            // save parsed result here

            position += line.Length + Environment.NewLine.Length; // count position
            progress = (int)(position * maximum / length); // calculate progress
            Percentage?.Report(progress); // report progress

            await Task.Delay(50); // just a delay for example.
                                  // never Thread.Sleep() in async methods!
        }
    }
    fs.Close();
}

private async Task<string> ParseLine(string line)
{
    string result = "";
    // do job here with 'line'
    return result;
}

private void ReportProgress(int value)
{
    ParsingStatus.Value = value;
    ParsingPercentage.Text = value + "%";
}

The key feature of IProgress is passing callback execution to the Thread where it was created.

About splitting UI and other App logiс you may use MVVM programming pattern.

aepot
  • 4,558
  • 2
  • 12
  • 24
  • Thank you for your answer. I'm exploring your solution, adjusting it to my current code. Still I feel you explained only the "hidden" part of the question, i.e. the concern about BackgroundWorker. My main aim is actually to avoid processing the results of parsing (this thing: `string result = await ParseLine(line);`) inside the UI logic. So I'd like to report the progress from the outside, and passing the adequate arguments as a reference was my idea to do this. Should I consider trying it with what you proposed (as you told me that BackgroundWorker is not the ultimate solution)? – J S May 13 '20 at 20:13
  • @JS [TAP](https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap) `await` executes the awaiter method not in UI thread, it's totally `async`. But the code which follows it will be executed in UI `SinchronizationContext` if you'll not set `.ConfigureAwait(false)`. On other hand if you want totally split UI logic and other code, use [`MVVM`](https://learn.microsoft.com/en-us/archive/msdn-magazine/2009/february/patterns-wpf-apps-with-the-model-view-viewmodel-design-pattern) – aepot May 13 '20 at 20:34
  • @JS and you may not use the whole example as it is but some ideas e.g. `IProgress`. – aepot May 13 '20 at 20:36
  • 1
    Ok, so as I understand, in your answer you focused on explaining how to split the execution of particular tasks and, you are right, I was thinking on splitting also the code. Thanks for the suggestion about `MVVM` - I'd look on it, but I'd see if it's worth to use in my project. Concerning your comment about using the example, that's just what I meant when I said "adjusting it to my current code" - to use some ideas. I'm accepting your answer and editing the question so it shows my intentions rather than what I've done. – J S May 13 '20 at 21:10
  • And yet another side question - can you tell is it a common practice to actually split the UI logic and other code in WPF apps, or is it rather common to put them together (at least in simple projects)? – J S May 13 '20 at 21:18
  • @JS yes, it's a common practice mainly caused by Unit Testing approach. And MVVM is the most popular programming pattern to solve that easily. – aepot May 13 '20 at 21:28
  • @JS I write a lot of **MVVM** examples on StackOverflow. You can find the content (maybe useful) in the answers located in my profile. – aepot May 13 '20 at 21:31
  • 1
    thanks, that's what I imagined, so here's why I try to learn WPF/C# that way from the beginning. – J S May 13 '20 at 21:34
  • 1
    I'll take a look for sure. – J S May 13 '20 at 21:35
  • you can consider adding the info about MVVM to the answer, because I think someone else could find it pretty useful. – J S May 13 '20 at 21:40
  • @JS added to the answer – aepot May 13 '20 at 22:01