1
votes

UPDATED With Improved Detail Source Code

Context: I am building a Winforms application using an MVC architecture. My View contains a Search Button, a ProgressBar, and BackgroundWorker control.

Events

        this.wrkBackgroundSearch.WorkerReportsProgress = true;
        this.wrkBackgroundSearch.WorkerSupportsCancellation = true;
        this.wrkBackgroundSearch.DoWork += new System.ComponentModel.DoWorkEventHandler(this.wrkBackgroundSearch_DoWork);
        this.wrkBackgroundSearch.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.wrkBackgroundSearch_RunWorkerCompleted);
        this.wrkBackgroundSearch.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.wrkBackgroundSearch_ProgressChanged);

The BackgroundWorker (wrkBackgrounSearch)'s DoWork event:

private void wrkBackgroundSearch_DoWork(object sender, DoWorkEventArgs e)
{
    BatchReaderController backgroundCnt = new BatchReaderController();
        BackgroundWorker worker = sender as BackgroundWorker;

        IDictionary<int, object> args = (IDictionary<int, object>)e.Argument;
        //Breaking the arg list down
        Int32 docType                       = (Int32)args[docTypeArgKey];
        Int32 chosenSearchElement           = (Int32)args[searchElementArgKey];
        Int32 environment                   = (Int32)args[environmentArgKey];

        try
        {
            e.Result = backgroundCnt.PerformSearch(docType, chosenSearchElement, environment, criteria, isFilenameSearch, worker, totalFileCount, directoriesToSearch, fileNameMask, xPath);
        }
        catch (Exception ex)
        {
            this.ShowError(ex.Message, "Error while searching", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
}

I also have this event:

private void wrkBackgroundSearch_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    prgSearchProgress.Value = e.ProgressPercentage;
}

Finally, inside the custom BatchReaderController object's PerformSearch method I have PerformXPathSearch method. (This is the I/O-intensive operation that runs in the background, and is supposed to provide async updates.)

private IList<BatchFileData> PerformXPathSearch(IList<String> uncPaths, Int32 docType, Int32 searchElement, String searchCriteria, BackgroundWorker worker, Int64 totalFileCount, String inputFileMask, String inputXPathQuery)
    {
        Int64 numberFilesSearched = 0;
        IList<BatchFileData> searchResults = new List<BatchFileData>();

        //Validate inputs

        //Look in each drive that was input
        foreach (String networkPath in uncPaths)
        {
            DirectoryInfo dir;
            FileInfo[] files = null;
            try
            {
                dir = new DirectoryInfo(networkPath);
            }
            catch (Exception ae)
            {
                throw new Exception("Bad directory path: " + ae.Message, ae);
            }


            if (!dir.Exists)
            {
                continue;
            }

            try
            {
                files = dir.GetFiles(inputFileMask, SearchOption.TopDirectoryOnly);
            }
            catch (Exception ae)
            {
                throw new Exception("Invalid filename mask: " + filenameMask, ae);
            }

            foreach (FileInfo file in files)
            {
                numberFilesSearched++;
                Boolean shouldOpenFile = DetermineWhetherToOpenFile(file.CreationTime);
                XmlDocument xmlDoc;
                XPathNavigator nav;
                XPathNavigator searchNode;
                DataSet xmlAsDataSet;

                if (!shouldOpenFile)
                {
                    Int32 percentComplete = CalculatePercentComplete(totalFileCount, numberFilesSearched);
                    worker.ReportProgress(percentComplete, searchResults.Count);
                    continue;
                }

                try
                {
                    using (FileStream fs = file.OpenRead())
                    {
                        xmlDoc = new XmlDocument();
                        xmlDoc.Load(fs);
                    }
                }
                catch (UnauthorizedAccessException uae)
                {
                    throw new UnauthorizedAccessException("Unable to read path to file: " + file.FullName, uae);
                }
                nav = xmlDoc.CreateNavigator();

                try
                {
                    searchNode = nav.SelectSingleNode(inputXPathQuery);
                }
                catch (ArgumentException ae)
                {
                    throw new ArgumentException("Argument Exception performing node select: " + xpathQuery, ae);
                }
                catch (XPathException xpe)
                {
                    throw new XPathException("Xpath error while performing node select: " + xpathQuery, xpe);
                }

                //If search results returns criteria success, add this data set to the list for display.
                if (searchNode != null)
                {
                    //Capture data for later processing
                }

                Int32 percentageComplete = CalculatePercentComplete(totalFileCount, numberFilesSearched);
                worker.ReportProgress(percentageComplete, searchResults.Count);
            }
        }

        return searchResults;
    }

Calculate Percent Complete

private Int32 CalculatePercentComplete(Int64 totalFileCount, Int64 numFoldersSearched)
    {
        Int32 percentAsInt;
        float searchPercent;

        searchPercent = numFoldersSearched / totalFileCount;

        try
        {
            percentAsInt = Convert.ToInt32(searchPercent);
        }
        catch (OverflowException oe)
        {
            throw new OverflowException("Error getting percent complete: " + oe.Message, oe);
        }

        if ((percentAsInt < 0) || (percentAsInt > 100))
        {
            throw new ArithmeticException("Invalid percentage: " + percentAsInt);
        }

        return percentAsInt;
    }

Problem: When testing, my ProgressBar does not receive the updates, even though while stepping through the code, the ProgressChanged event IS being fired.

I have also tried several iterations of the following, after checking threads on StackOverflow, within the ProgressChanged event:

private void wrkBackgroundSearch_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    prgSearchProgress.Invoke(new PerformProgrssUpdate(this.DisplaySearchProgress),
        new object[]{e.ProgressPercentage});
    //if (prgSearchProgress.InvokeRequired)
    //{
    //    prgSearchProgress.Invoke();
    //}
}

private void DisplaySearchProgress(Int32 percentComplete)
{
    prgSearchProgress.Value = percentComplete;
}

public delegate void PerformProgrssUpdate(Int32 percentComplete);

Note the commented-out attempt of another solution. The search operation will complete successfully, and when the search operation is completed, the ProgressBar control has its value updated to (1/n)% complete, as the search finishes.

Question How do I make this work, such that when my Controller performs the I/O-intense search, the Progress Bar in my view is updated appropriately, so that the user knows some function is being executed?

2
Is the displaysearhprogress called on the UI thread? You can check that while debuggingSievajet
How can I check this?Matt
I'm not buying, particularly since it actually updates. Occam's razor says that you are simply calculating the progress percentage wrong. As posted, the worker.ReportProgress call in PerformSearch() method should pass 100 since it is the last operation.Hans Passant
Btw you set the result in 1 function call as i can see. I dont see any loop for your progress updates. With the code you posted, the progresschanged is fired onceSievajet
Now I think you've gone to far the other way - there is way too much code. See the article that @PeterDuniho has linked. Rather than giving us a filtered version of your real code, or your entire code, you should try to create a minimal version that is complete and demonstrates the issue. Start with a fresh project and start building it slowly by incorporating minimal versions of your current code. You should eventually figure the issue out.psubsee2003

2 Answers

1
votes

It's a classic:

Int32 CalculatePercentComplete(Int64 totalFileCount, Int64 numFoldersSearched)
{
  Int32 percentAsInt;
  float searchPercent;

  searchPercent = numFoldersSearched / totalFileCount;
  // searchPercent will be 0.0 here as long as numFoldersSearched < totalFileCount

  ...
}

The fact that searchPercent is a float does not change that numFoldersSearched/totalFileCount is an integer division.

5L / 6L == 0L
0
votes

The solution to my problem lay in how I was determining the Percent Complete for the Worker's ProgressChanged event. Below is the final solution:

private Int32 CalculatePercentComplete(Int64 totalFileCount, Int64 numFoldersSearched)
    {
        Int32 percentAsInt;
        Decimal searchPercent;

        searchPercent = (Decimal)numFoldersSearched / (Decimal)totalFileCount;

        try
        {
            Decimal x = Math.Round(searchPercent*100, 0, MidpointRounding.ToEven);
            percentAsInt = Convert.ToInt32(x);
        }
        catch (OverflowException oe)
        {
            throw new OverflowException("Error getting percent complete: " + oe.Message, oe);
        }

        if ((percentAsInt < 0) || (percentAsInt > 100))
        {
            throw new ArithmeticException("Invalid percentage: " + percentAsInt);
        }

        return percentAsInt;
    }