Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

using a background worker but wait for completion.

Status
Not open for further replies.

1DMF

Programmer
Jan 18, 2005
8,795
GB
I am very confused over WPF , C# and allowing the GUI to update.

I'm trying to create an FTP helper class based on :
I want to pop open a progress window with a progress bar to show the progress of the FTP.

The problem as I understand WPF is if you start a long running process on the GUI thread then any attempted update to the GUI will not update, so you have to use a worker process.

All examples I have found for a progress update uses an asynchronous worker process, but that doesn't make sense.

How can I have a method that returns Boolean as to the success of the FTP if the worker process called is Async?

I don't want to tie up the GUI/UI thread but I also don't want the return to the client from the method call to happen until the process either succeeded or failed.

here is what I have...
Code:
   /// <summary>
    /// Interaction logic for ProgressBar.xaml
    /// </summary>
    public partial class ProgressBar : Window
    {
        public ProgressBar()
        {
            InitializeComponent();
        }
    }

 public class FTP
    {
        // private attributes
        private ProgressBar pgbar;
        private string HostName { get; set; }
        private string UserName { get; set; }
        private string Password { get; set; }
        private string LocalFile { get; set; }
        private string RemoteFile { get; set; }
        private string RemoteDir { get; set; }
        private int counter { get; set; }
        private int total { get; set; }

        // public properties
        public bool OK { get; private set; }
       
        // Constructor
        public FTP() : this(new ProgressBar())
        {

        }

        // Constructor with own ProgressBar
        public FTP(ProgressBar pgb)
        {
            this.pgbar = pgb;
            this.pgbar.ShowDialog();

        }


        // download a file
        public bool Get(string hostname, string username, string password, string localfilename, string remotefilename, 
                            string remotedirectory, int counter = 1, int total = 1)
        {
            // set attributes for worker process
            this.counter = counter;
            this.total = total;
            this.HostName = hostname;
            this.UserName =  username;
            this.Password = password;
            this.LocalFile = localfilename;
            this.RemoteFile = remotefilename;
            this.RemoteDir = remotedirectory;

            // init progress
            this.InitProgress();

            BackgroundWorker worker = new BackgroundWorker();
            worker.WorkerReportsProgress = true;
            worker.DoWork += FTP_Get;
            worker.ProgressChanged += UpdateProgress;
[COLOR=#FCE94F]            worker.RunWorkerAsync();

         // STUCK HERE - 

         return this.OK;
[/color]
        }

        private void InitProgress()
        {
            this.pgbar.Desc.Content = "Downloading " + this.counter + " of " + this.total + " (" + this.RemoteFile + ")";
            this.pgbar.Progress.Value = 0;
            this.pgbar.Perc.Text = "0%";

        }

        // FTP download helper
        private void FTP_Get(object sender, DoWorkEventArgs e)
        {
            using (FtpClient conn = new FtpClient())
            {
                conn.Host = this.HostName;
                conn.Credentials = new NetworkCredential(this.UserName, this.Password);
                conn.DataConnectionEncryption = true;

                using (Stream istream = conn.OpenRead(Path.Combine(this.RemoteDir, this.RemoteFile)))
                {
                    
                    try
                    {
                        // istream.Position is incremented accordingly to the reads you perform
                        // istream.Length == file size if the server supports getting the file size
                        // also note that file size for the same file can vary between ASCII and Binary
                        // modes and some servers won't even give a file size for ASCII files! It is
                        // recommended that you stick with Binary and worry about character encodings
                        // on your end of the connection.
                    }
                    finally
                    {
                        istream.Close();
                    }
                }
            }

        }
    }
}

I haven't fully implemented the FTP code yet, but am stuck with understanding how I run a background worker, keeping the UI thread free so the progress bar works, but not retuning from the FTP.Put() method until the background work has finished?

I cannot see a "RunWorkerSynch" method, to run it synchronously, so am I using the wrong type of worker thread?

your help is appreciated.

1DMF.





"In complete darkness we are all the same, it is only our knowledge and wisdom that separates us, don't let your eyes deceive you."

"If a shortcut was meant to be easy, it wouldn't be a shortcut, it would be the way!"
Free Electronic Dance Music
 
Hi 1DMF,

it is not that complicated and you are almost there already. ;-)

What is missing is to add another event that fires once the worker is done:
Code:
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
And then:
Code:
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            progbar1.Value = 0;
            lblCurFil.Content = "";
            System.Windows.Forms.MessageBox.Show("I'm done!");
        }

Does that help?

P.S: Once you get used to working with background workers I am pretty sure you will learn to love them.
:)

Cheers,
MakeItSo

"Knowledge is power. Information is liberating. Education is the premise of progress, in every society, in every family." (Kofi Annan)
Oppose SOPA, PIPA, ACTA; measures to curb freedom of information under whatever name whatsoever.
 
Hi MakeItSo,

Thanks for the reply. Isn't this still Asynchronous?

I need to explain a little more I think...

I have a form, that I plan to use MVVM, so I have a view model, which in turn will use my business model.

In that business model I want to FTP some files, using my helper FTP class.
Code:
class BusinessModel {

    public bool GetFile()
    {

            FTP ftp = new FTP();
            return ftp.Get("localhost","user","password","c:\testing.txt","testing.txt",@"\");
    
    }

}

How can the method in the FTP class return boolean success until the process has finished?

As I showed (sorry meant to highlight not colour yellow, so you may have missed it)
Code:
public bool Get {

....

    worker.RunWorkerAsync();

    [highlight #FCE94F]// STUCK HERE - This next line should not run until the FTP worker process has finished.[/highlight]

    return this.OK;
}

How can I call a method that returns a boolean success flag, which itself runs a long running process on another thread asynchronously?

even if I include the completed event handler....
Code:
public bool Get {

....

    worker.RunWorkerCompleted += worker_RunWorkerCompleted; 
    worker.RunWorkerAsync();
[highlight #FCE94F]
    // How does including a worker completed event handler stop the next line of code running until the FTP process finishes?[/highlight]

    return this.OK;
}

Do I forget the background process and just force WPF to update the GUI using

Code:
this.pgbar.Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.Render);
Which seems to do the same thing as DoEvents does in VB?





"In complete darkness we are all the same, it is only our knowledge and wisdom that separates us, don't let your eyes deceive you."

"If a shortcut was meant to be easy, it wouldn't be a shortcut, it would be the way!"
Free Electronic Dance Music
 
Yes, it is still Asynchronous - which is the purpose of a background worker.
You could of course check:
Code:
While worker.IsBusy
   System.Threading.Thread.Sleep(1000);
But that is not the proper way to handle things here. If you use a worker, then make use of that worker and return the value from the RunWorkerCompleted handler, not from your GetFile method.

I usually do lots of file processing, not FTP connections, but here is how I am using the worker in a WPF application:

1.) All "heavy lifting" is done by the worker.
2.) The progress of the work is being reported by worker_ProgressChanged, e.g. like this:
Code:
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            lblCurFil.Content = ((string)e.UserState);  //Name of file currently being processed
            progbar1.Value = e.ProgressPercentage;
        }
3.) Whatever needs to be fired after the worker has completed, I fire it from worker_RunWorkerCompleted.
If this requires some more complex object to be passed to Completed event, I simply do this at the end of my processing loop in the worker:
Code:
e.Result=myObject
And access it (for example a Dictionary<int, string>) in the Completed method like this:
Code:
var myObject = (Dictionary<int, string>)e.Result

Cheers,
MakeItSo

"Knowledge is power. Information is liberating. Education is the premise of progress, in every society, in every family." (Kofi Annan)
Oppose SOPA, PIPA, ACTA; measures to curb freedom of information under whatever name whatsoever.
 
So basically it is impossible to return any success value from a calling method that uses a background worker.

Therefore my business logic can never know synchronously if it should continue with the next part of the code.

OK thanks, I guess a worker process is not what I'm looking for then as there is no way I'm ever implementing
Code:
While worker.IsBusy
   System.Threading.Thread.Sleep(1000);

I sometimes miss MS Access, things were so much simpler with the built in progress meter
Code:
' Initialise progress meter
RetVal = SysCmd(acSysCmdInitMeter, "Uploading File '" & RemoteFileName & "' - " & iCnt & " of " & iTot, (iSize / 1000))

Oh well, I guess they call it progress!






"In complete darkness we are all the same, it is only our knowledge and wisdom that separates us, don't let your eyes deceive you."

"If a shortcut was meant to be easy, it wouldn't be a shortcut, it would be the way!"
Free Electronic Dance Music
 
So basically it is impossible to return any success value from a calling method that uses a background worker.
It is not impossible at all.
Think of it more in an MVC or AJAX way: the worker is your controller, it is the one executing the process and therefor it is the one that should return a success value.
Your Get(...) method runs in the Form display thread. It corresponds to your View and should not do much code execution. Instead the worker should "push" whatever is required to the form thread at the given time.

WPF is not VB6. Workers enable you to do really heavy lifting without your window freezing up :)


"Knowledge is power. Information is liberating. Education is the premise of progress, in every society, in every family." (Kofi Annan)
Oppose SOPA, PIPA, ACTA; measures to curb freedom of information under whatever name whatsoever.
 
Sorry can't can't get my head round it.

How can an asynchronous process return a synchronous value?

WPF is not VB6. Workers enable you to do really heavy lifting without your window freezing up
Which in theory sounds great, but in practice I seem unable to synchronously run a process that MUST report success or failure and only continue if successful and update the GUI at the same time?

I must have a brain freeze somewhere and need to alter my app design, but I don't understand how. A synchronous dependent process running in an asynchronous thread does not compute with me.





"In complete darkness we are all the same, it is only our knowledge and wisdom that separates us, don't let your eyes deceive you."

"If a shortcut was meant to be easy, it wouldn't be a shortcut, it would be the way!"
Free Electronic Dance Music
 
I apologize to any whiz out there / in here for the following blatant terminology abuse but here i go anyway:

1.) you call worker_DoWork asynchronously, the process then runs on a separate thread in the background
2.) with every ".ReportProgress" you push values from the background thread to your foreground, enabling you to display progress on your form.
3.) with worker_RunWorkerCompleted you return back to your foreground / form thread for good.

Now if you need to call any method only after some process has finished, all you need to do is call that process from worker_RunWorkerCompleted:
Code:
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if((string)e.Result=="Success") //or whatever value you pass in the result object to tell "yeah, go on"
            {
                MethodToProceed(...);
            }
        }

You need to get away from the notion of handling it all in you Get(...) method. Use it to initiate the processing only. Let the processing be done by the worker(s) and call other methods/workers depending on the parameter/success criteria you pass with e.result respectively.

Does that make any sense?

"Knowledge is power. Information is liberating. Education is the premise of progress, in every society, in every family." (Kofi Annan)
Oppose SOPA, PIPA, ACTA; measures to curb freedom of information under whatever name whatsoever.
 
sorry, still confused...

Scenario...

I have a housekeeping job that the user wants to run via a button on a form (or menu option).

The form has no behind code what so ever, it has an event listener via MVVM for the button click.

The MVVM uses a business logic model class to perform the steps A B C.

Step A is to FTP a file.

To do this the business logic model class uses a helper FTP class.

Step B must only run if step A succeeds and step C only if A & B succeeds.

The worker process is performed in the FTP class

So the business model needs to know if the FTP was successful before continuing with the next step.

With a work complete event that would be in the FTP helper class wouldn't it? along with the instantiation of the worker thread, so how does that help the business logic model to know if it's OK to perform the next step?

The call to Get() in the FTP class would return to the business logic model before the worker process has finished?
Code:
ApplicationForm -> MVVM -> BusinessLogicModel -> FTPHelperClass
                                                      ||
                                                      \/
                                               ProgressBarPopUpWindow
Hopefully the above relationship helps?






"In complete darkness we are all the same, it is only our knowledge and wisdom that separates us, don't let your eyes deceive you."

"If a shortcut was meant to be easy, it wouldn't be a shortcut, it would be the way!"
Free Electronic Dance Music
 
Perhaps WPF is not for me.

I seem unable to run a synchronous process and update the GUI simultaneously.

Even going down the empty action road, draws a blank...

Code:
                try
                {
                    using (Stream stream = conn.OpenRead(Path.Combine(this.RemoteDir, this.RemoteFile)))
                    {
                        try
                        {

                            // init progress
                            this.InitProgress(stream.Length);
                            byte[] buf = new byte[8192];
                            int read = 0;
                            while ((read = stream.Read(buf, 0, buf.Length)) > 0)
                            {
                                [highlight #FCE94F]this.UpdateProgress(stream.Position);[/highlight]
                            }

                        }
                        catch(Exception e)
                        {
                            this.Status.OK = false;
                            this.Status.Msg = e.Message;
                        }
                        finally
                        {
                            stream.Close();
                        }
                    }
                }

        // progress update helper
        private void UpdateProgress(long val)
        {
           [highlight #FCE94F] this.pgbar.Dispatcher.Invoke(new Action(() =>{}), DispatcherPriority.Render);[/highlight] 
            this.pgbar.Progress.Value = val;
            this.pgbar.Perc.Text = string.Format("{0:N2}%", (val / this.pgbar.Progress.Maximum) * 100 );

        }

The progress moves to @ 10% smoothly exactly as desired, but then suddenly stops, then jumps to 100% at the end. It seems the render dispatcher action only works for a few seconds then the GUI just freezes and doesn't update till the entire process finishes.

Last resort is to see if I can get the progress window to run in the background process instead?



"In complete darkness we are all the same, it is only our knowledge and wisdom that separates us, don't let your eyes deceive you."

"If a shortcut was meant to be easy, it wouldn't be a shortcut, it would be the way!"
Free Electronic Dance Music
 
OK, just when I was about to throw the towel in I found a resolve...

I moved the init & update code to progress form, and decided to play with the action and use it to perform the update also changing the dispatcher priority
Code:
public partial class ProgressBar : Window
    {
        public ProgressBar()
        {
            InitializeComponent();
        }

        public void InitProgress(long max, string info)
        {
            this.Desc.Content = info;
            this.Progress.Maximum = max;
            this.Progress.Value = 0;
            this.Perc.Text = "0%";
            this.Show();
        }

        public void UpdateProgress(long pos)
        {
[highlight #FCE94F]            this.Dispatcher.Invoke(new Action(() =>
            {
                this.Progress.Value = pos;
                this.Perc.Text = string.Format("{0:N2}%", (pos / this.Progress.Maximum) * 100);
            }), DispatcherPriority.ContextIdle);[/highlight]

        }
    }

I now have an amazingly fast FTP helper class that works synchronously reporting its success while having an interactive progress bar showing the user details of the FTP progress in real-time!

Boy was that painful!

"In complete darkness we are all the same, it is only our knowledge and wisdom that separates us, don't let your eyes deceive you."

"If a shortcut was meant to be easy, it wouldn't be a shortcut, it would be the way!"
Free Electronic Dance Music
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top