views:

386

answers:

5

I am learning to program in C# and have most of the basics down already. I am having trouble using the background worker and using it with multiple classes. This is a backup program that I am writing I have the following classes.

lacie.cs ---> used to search for backup device main.cs ---> Main entry size.cs ---> Determines the size of the backup xml.cs ---> Reads an xml config file of directories to be backed up.

I will show what I have in the main.cs so far.

[main.cs code]

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace QXBackup
{
    class main
    {
        static void Main(string[] args)
        {
            lacie BackupDrive = new lacie();
            BackupDrive.findLacie();

            xml xmlFile = new xml();
            xmlFile.ProcessXML();

            size BackupSize = new size();
            BackupSize.GetSize(xmlFile.Path);

            int SizeofBackup = (int)(((BackupSize.BackupSize) / 1024f) / 1024f) / 1024;
            Console.WriteLine("Drive Letter: " + BackupDrive.Drive);
            Console.WriteLine("Volume Name: " + BackupDrive.VolumeLabel);
            Console.WriteLine("Free Space: " + Convert.ToString(BackupDrive.AvailableSize) + "G");
            Console.WriteLine("Size of Lacie: " + Convert.ToString(BackupDrive.TotalSize) + "G");
            Console.WriteLine("Backup Size: " + Convert.ToString(SizeofBackup + "G"));
            Console.WriteLine("Backing up " + BackupSize.FileCount + " files found in " + BackupSize.FolderCount + " folders.");
            Console.ReadKey(true);
        }
    }
}

[end main.cs code]

now the program works fine so far and displays what I have asked it to print on the screen. My problem is as follows. When it goes off to calculate the size of the backup job the program just sits there waiting for teh size.cs class to return the value. I want to be able to use the background worker to have the program load and update the size number as it is calculating the size and show that on the screen. This is a console program and I am not sure if I will be able to do that but it will also help me as I plan to in the future turn this into a GUI based program. Can some one help me with this I have been trying all kinds of things and nothing works. I think my confusion is were to introduce the background worker and how to implement it correctly. Thanks for the help

+2  A: 

As your question requests, this shows you how to use the background worker component. This should suffice, however, when you get into more complex thread usage, it'd be better to write something like aaronls has provided.

Your Size class should hold the backgroundWorker thread and BackupSize.GetSize("PathHere") should make an asynchronous call to start the background worker thread.

in your do work method you can report progress of the work by calling backgroundWorker.ReportProgress(i);

Inside of your ReportProgress delegate you can create an event notification that can be hooked in to by your main gui

class main
{
    static void Main(string[] args)
    {

        size BackupSize = new size();
        BackupSize.GetSize("path");
        BackupSize.ProgressEvent += new ProgressEventHandler(BackupSize_ProgressEvent);

        // BackupSize.BackupSize will not be accurate until the thread is finished.
        // You may want to take that into consideration
        int SizeofBackup = (int)(((BackupSize.BackupSize) / 1024f) / 1024f) / 1024;

        Console.ReadLine();

    }

    static void BackupSize_ProgressEvent(object source, int progress)
    {
        Console.WriteLine(String.Format("Progress: {0}", progress));
    }
}

// This is the delegate that acts as the event handler for your progress events
public delegate void ProgressEventHandler(object source, int progress);

public class size
{
    private readonly BackgroundWorker backgroundWorker;
    public event ProgressEventHandler ProgressEvent;

    public size()
    {
        backgroundWorker = new BackgroundWorker { WorkerReportsProgress = true };
        backgroundWorker.DoWork += backgroundWorker_DoWork;
        backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
    }

    public void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        // do something with progress
        ProgressEvent.Invoke(sender, e.ProgressPercentage);
    }

    void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        String Path = e.Argument as String;

        // Do something with Path;

        // Simulate work
        for (int i = 1; i <= 100; i++)
        {
            // Get the size of the Here

            // Report the Progress
            backgroundWorker.ReportProgress(i);        
            Thread.Sleep(10);
        }
    }

    public void GetSize(String path)
    {
        backgroundWorker.RunWorkerAsync(path);
    }
}
Michael G
+1 I would start with Michael's example instead of mine. See how much simpler Michael's example is because he used the specialized BackgroundWorker. You are however limited in what data you can report through the events. I have always preferred to implement my own thread management class and events so that I can have more specific strongly typed objects passed through the events, but this involves alot of extra code.
AaronLS
+2  A: 

Maybe this code I've used as an example before will help you out. This is just using basic threading, but it's similar to how a BackgroundWorker works in that it uses events to signal completion and updates. If you look where there is the comment "signal completion by firing an event" this is firing an event to indicate the task is done and return some information. You could just as well create other types of events like a ProgressUpdate event and fire it repeatedly from a CalculateSize thread so that you can gradually update a ProgressBar. Now I would actually have the below split up into multiple classes and not all mashed together, but you get the idea. You'd have the stuff related to handling the event in one class, the subscriber which would probably be your Form, and then the threading and processing would be in your class that does the Size calculation.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace ThreadWithDataReturnExample
{
    public partial class Form1 : Form
    {
        private Thread thread1 = null;

        public Form1()
        {
            InitializeComponent();

            thread1 = new Thread(new ThreadStart(this.threadEntryPoint));
            Thread1Completed += new AsyncCompletedEventHandler(thread1_Thread1Completed);
        }

        private void startButton_Click(object sender, EventArgs e)
        {
            thread1.Start();
            //Alternatively, you could pass some object
            //in such as Start(someObject);
            //With apprioriate locking, or protocol where
            //no other threads access the object until
            //an event signals when the thread is complete,
            //any other class with a reference to the object 
            //would be able to access that data.
            //But instead, I'm going to use AsyncCompletedEventArgs 
            //in an event that signals completion
        }

        void thread1_Thread1Completed(object sender, AsyncCompletedEventArgs e)
        {
            if (this.InvokeRequired)
            {//marshal the call if we are not on the GUI thread                
                BeginInvoke(new AsyncCompletedEventHandler(thread1_Thread1Completed),
                  new object[] { sender, e });
            }
            else
            {
                //display error if error occurred
                //if no error occurred, process data
                if (e.Error == null)
                {//then success

                    MessageBox.Show("Worker thread completed successfully");
                    DataYouWantToReturn someData = e.UserState as DataYouWantToReturn;
                    MessageBox.Show("Your data my lord: " + someData.someProperty);

                }
                else//error
                {
                    MessageBox.Show("The following error occurred:" + Environment.NewLine + e.Error.ToString());
                }
            }
        }

        #region I would actually move all of this into it's own class
            private void threadEntryPoint()
            {
                //do a bunch of stuff

                //when you are done:
                //initialize object with data that you want to return
                DataYouWantToReturn dataYouWantToReturn = new DataYouWantToReturn();
                dataYouWantToReturn.someProperty = "more data";

                //signal completion by firing an event
                OnThread1Completed(new AsyncCompletedEventArgs(null, false, dataYouWantToReturn));
            }

            /// <summary>
            /// Occurs when processing has finished or an error occurred.
            /// </summary>
            public event AsyncCompletedEventHandler Thread1Completed;
            protected virtual void OnThread1Completed(AsyncCompletedEventArgs e)
            {
                //copy locally
                AsyncCompletedEventHandler handler = Thread1Completed;
                if (handler != null)
                {
                    handler(this, e);
                }
            }
        #endregion

    }
}
AaronLS
This solution offers greater flexibility.
Michael G
A: 

Instead of using BackgroundWorker as-is, consider inheriting from it. This gives you more flexibility about how you get data in and out. Consider this example, where you pass in data in the constructor and get it out with a property:

class BackgroundDoerOfSomething : BackgroundWorker
{
    string _SomeData;

    public string SomeResult { get; private set; }

    public BackgroundDoerOfSomething(string someData)
    {
        _SomeData = someData;
    }

    protected override void OnDoWork(DoWorkEventArgs e)
    {
        base.OnDoWork(e);
        // do some processing, and then assign the result
        SomeResult = "some other data";
    }
}

You would use it like this:

class DoSomethingInBackground
{
    BackgroundDoerOfSomething _doer;

    void DoSomething()
    {
        _doer = new BackgroundDoerOfSomething("abc");
        _doer.RunWorkerCompleted += _doer_RunWorkerCompleted;
        _doer.RunWorkerAsync();
    }

    void _doer_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        var myResult = _doer.SomeResult;
        // then do something with the result
    }
}
Kyralessa
A: 

See if this could give you a clue on background worker. If this is in the windows form context, you like to use BeginInvoke of the control to update the UI by its owning thread. e.g. txtMessage.Invoke(UpdateMyMsg)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace QXBackup
{
    class main
    {
        static void Main(string[] args)
        {
            var bgWorker = new BackgroundWorker();
            bgWorker.WorkerReportsProgress = true;

            bgWorker.DoWork += (sender, e) =>
            {
                lacie BackupDrive = new lacie();
                BackupDrive.findLacie();

                xml xmlFile = new xml();
                xmlFile.ProcessXML();

                size BackupSize = new size();
                BackupSize.GetSize(xmlFile.Path);

                int SizeofBackup = (int)(((BackupSize.BackupSize) / 1024f) / 1024f) / 1024;
                Console.WriteLine("Drive Letter: " + BackupDrive.Drive);
                Console.WriteLine("Volume Name: " + BackupDrive.VolumeLabel);
                Console.WriteLine("Free Space: " + Convert.ToString(BackupDrive.AvailableSize) + "G");
                Console.WriteLine("Size of Lacie: " + Convert.ToString(BackupDrive.TotalSize) + "G");
                Console.WriteLine("Backup Size: " + Convert.ToString(SizeofBackup + "G"));
                Console.WriteLine("Backing up " + BackupSize.FileCount + " files found in " + BackupSize.FolderCount + " folders.");
                Console.ReadKey(true);
            };

            bgWorker.RunWorkerCompleted += (sender, e) => Console.WriteLine("completed...");
            bgWorker.ProgressChanged += (sender, e) => Console.WriteLine("progressing...");


            bgWorker.RunWorkerAsync();
        }
    }
}
codemeit
+1  A: 

Without taking into account needing to update a GUI (which would require doing a control.Invoke()), here is a very easy way to run something in the background using the ThreadPool. The great thing about the thread pool is that you don't have to worry about creating, disposing and keeping track of threads.

static void Main(string[] args)
    {
        lacie BackupDrive = new lacie();
        BackupDrive.findLacie();

        xml xmlFile = new xml();
        xmlFile.ProcessXML();

        size BackupSize = new size();
        System.Threading.ThreadPool.QueueUserWorkItem(s =>
        {
            BackupSize.GetSize(xmlFile.Path);
        });

        int SizeofBackup = (int)(((BackupSize.BackupSize) / 1024f) / 1024f) / 1024;
        Console.WriteLine("Drive Letter: " + BackupDrive.Drive);
        Console.WriteLine("Volume Name: " + BackupDrive.VolumeLabel);
        Console.WriteLine("Free Space: " + Convert.ToString(BackupDrive.AvailableSize) + "G");
        Console.WriteLine("Size of Lacie: " + Convert.ToString(BackupDrive.TotalSize) + "G");
        Console.WriteLine("Backup Size: " + Convert.ToString(SizeofBackup + "G"));
        Console.WriteLine("Backing up " + BackupSize.FileCount + " files found in " + BackupSize.FolderCount + " folders.");
        Console.ReadKey(true);
    }

You could add something else to the thread pool that just kept writing to the console like this:

System.Threading.ThreadPool.QueueUserWorkItem(s =>
        {
            while (true)    // might want a real condition like while(!backupNotDone)
            {
                int SizeofBackup = (int) (((BackupSize.BackupSize)/1024f)/1024f)/1024;
                Console.WriteLine("Backup Size: " + Convert.ToString(SizeofBackup + "G"));
            }
        });
Tim Clem
I like this, it's very clean; but like you said; GUI notification makes this a little harder to do. Also, BackupSize property will not show the correct information until the thread has finished. ThreadPool.QueueUserWorkItem is queues the action to be exectuted, BackupSize on the next line will be at it's initialized value.
Michael G
Yes, you are right.
Tim Clem