Search code examples
c#wpfmultithreadingbackgroundworker

Backgroundworker not Rendering Window fluently


I just got into WPF and am currently trying my luck with the Background worker, so I figured I'd just open any file using the FileOpenDialog, loop through all the bytes inside the file and report the total progress via worker.ReportProgress in percentage ... alas, this only works for like ~20 times and then it gets really stuck and suddenly stops at 100%.

Here's my code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Win32;
using System.IO;
using System.Threading;
using System.ComponentModel;

namespace BitStream
{
public partial class MainWindow : Window
{
    private int bytes = 0;
    private long length = 0;

    public MainWindow()
    {
        InitializeComponent();
    }

    private void selectFile_Click(object sender, RoutedEventArgs e)
    {
        BackgroundWorker bw = new BackgroundWorker();
        OpenFileDialog ofd = new OpenFileDialog();
        if ((bool)ofd.ShowDialog())
        {
            FileInfo fi = new FileInfo(ofd.FileName);
            this.length = fi.Length;
            bw.DoWork += bw_DoWork;
            bw.RunWorkerCompleted += bw_RunWorkerCompleted;
            bw.ProgressChanged += bw_ProgressChanged;
            bw.WorkerReportsProgress = true;
            Stream str = ofd.OpenFile();

            bw.RunWorkerAsync(str);
        }
    }

    private void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        Stream str = (Stream)e.Argument;
        int singleByte = 0;
        this.Dispatcher.Invoke(
                new Action(() =>
                {
                    int currentProgress = 0;
                    while ((singleByte = str.ReadByte()) != -1)
                    {

                        label1.Content = singleByte;
                        bytes++;

                        currentProgress = Convert.ToInt32(((double)bytes) / length * 100);
                        if (currentProgress > progress)
                        {
                            progress = currentProgress;
                            ((BackgroundWorker)sender).ReportProgress(progress);
                            Thread.Sleep(100);
                        }
                    }
                }

            ), System.Windows.Threading.DispatcherPriority.Render);
    }

    private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        label2.Content = e.ProgressPercentage + "% completed";
    }

    private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
    }
}

}

Labels 1 and 2 are there for showing the current byte and the current progress in %.

Feel free to also criticize every other aspect of my code, I just got started with WPF today.

Edited DoWork-Method:

 private void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        Stream str = (Stream)e.Argument;
        int singleByte = 0;

        int currentProgress = 0;
        while ((singleByte = str.ReadByte()) != -1)
        {
            this.Dispatcher.Invoke(
                new Action(() =>
                {
                    label1.Content = singleByte;
                }), System.Windows.Threading.DispatcherPriority.Render);
            bytes++;

            currentProgress = Convert.ToInt32(((double)bytes) / length * 100);
            if (currentProgress > progress)
            {
                progress = currentProgress;
                this.Dispatcher.Invoke(
                new Action(() =>
                {
                    ((BackgroundWorker)sender).ReportProgress(progress);


                }), System.Windows.Threading.DispatcherPriority.Render);
                Thread.Sleep(500);        
            }
        }
    }

Thanks,

Dennis


Solution

  • So assuming you really want to make a cross thread call for each byte (which I wouldn't recommend), the code would look something like:

    private void bw_DoWork(object sender, DoWorkEventArgs e) 
    { 
        Stream str = (Stream)e.Argument; 
        int singleByte = 0; 
        int currentProgress = 0; 
        while ((singleByte = str.ReadByte()) != -1) 
        { 
    
           bytes++; 
           this.Dispatcher.Invoke( 
                new Action(() => 
                { 
                        label1.Content = singleByte; 
                } 
    
            ), System.Windows.Threading.DispatcherPriority.Render); 
    
            currentProgress = Convert.ToInt32(((double)bytes) / length * 100); 
            if (currentProgress > progress) 
            { 
                progress = currentProgress; 
                ((BackgroundWorker)sender).ReportProgress(progress); 
                Thread.Sleep(100); 
            } 
        } 
    } 
    

    The idea being that you can only manipulate a DispatcherObject on the thread on which it was created.