# C# backgroundWorker Question



## dcf-joe (Feb 1, 2017)

I have written a small program in C Sharp in Visual Studio that basically just downloads a file from one of our company's repositories and then replaces whatever file is currently on a technician's laptop.

I have two backgroundWorkers that get ran whenever the button is clicked to begin the download. One worker is busy doing the actual SFTP download and the other worker is busy monitoring the file size of the file being downloaded to determine the download progress.

The issue I am running into is, half of the laptops are displaying the download progress and the other half are not displaying the download progress. All of them are actually downloading the file though. I can not understand why all of the laptops are not displaying the download progress. The code is the same, I only have one version of the program.

Troubleshooting I have done so far:

The laptops are either Windows 7 or Windows 10. I have physically verified that the program works correctly regardless of the OS (when it is working).
The program was built with the "Any CPU" flag, so 32/64 bit should not matter.
Verified that at least .NET Framework 4 was installed (that was my target framework)
What do you guys think?


----------



## eidairaman1 (Feb 1, 2017)

@FordGT90Concept might be able to help out in C#


----------



## BiggieShady (Feb 1, 2017)

Background worker is great when you have your own mechanism to report progress from background thread - usually it's serial calling of a thread blocking methods in the background thread and after each one a progress update to the main thread.
Since you are downloading a single file and trying to report progress of that same download, I suggest using *WebClient.DownloadFileAsync* method that doesn't block the calling thread and has built in events for reporting detailed progress such as *WebClient.DownloadProgressChanged* event. The consequence is that this way you don't need to wrap it up in the background worker.

Look at the example at https://msdn.microsoft.com/en-us/library/system.net.webclient.downloadprogresschanged(v=vs.110).aspx


----------



## dcf-joe (Feb 1, 2017)

This is the meat of the code that I have wrote, which works, but I can not figure out why it does not work on all computers.


```
// This thread will handle monitoring the downloaded BioR.mdb file size
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {        
            BackgroundWorker worker = sender as BackgroundWorker; // necessary

            do // continue reporting file size until the file has finished downloading
            {
                Thread.Sleep(1000); // report once per second

                long file_size = new FileInfo(@"C:\BioERemote\BioR.mdb").Length; // get file size
                worker.ReportProgress(Convert.ToInt32(file_size)); // "worker" reports the file size to ProgressChanged event
            } while (!is_complete); // is_complete becomes true when backgroundworker2 completes the download        
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            label2.Text = Convert.ToString(Math.Round((Convert.ToDouble(e.ProgressPercentage) / Convert.ToDouble(remote_size)) * 100.0, 2)) + "% Downloaded";
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            label2.Text = "";
        }

        private void backgroundWorker2_DoWork(object sender, DoWorkEventArgs e)
        {
            using (var client = new SftpClient(sftp_address, sftp_username, sftp_password))
            {
                client.Connect();
                DownloadDirectory(client, bioe_remote_source, local_remote_destination);
                client.Disconnect();
            }

            is_complete = true;
        }

        private void backgroundWorker2_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {          
            label_Status.ForeColor = Color.Green;
            label_Status.Text = "Completed! You may use Remote.";
        }
```


----------



## BiggieShady (Feb 1, 2017)

ReportProgress has 2 overloads:

public void ReportProgress(
   int percentProgress
)

public void ReportProgress(
   int percentProgress,
   object userState
)

These methods expect value from 0 to 100 as percentProgress, maybe you should use the second one - supply real percentage as first argument, and the file size as a userState object (or wrapped up in the same object).
I'm not sure if this is a cause - probably not, more likely a racing condition between two threads (download completes before one second wait and the complete is called clearing the label immediately after updating it with progress info)

To confirm this put one break point on "is_complete = true;" line, and another right after Thread.Sleep ... and see which one gets called first.

... or just don't clear the label2 in worker1 completed handler ... and leave 100% status in it


----------



## dcf-joe (Feb 1, 2017)

I posted the same question over on Stack Overflow and they immediately saw the issue . I can't believe I didn't think of it.

I needed to put a try catch statement around my FileInfo.Length statement. I was assuming every one of our technicians had a fast connection to our repository. So, I can only assume on slower connections, after the initial one second delay, the BioR.mdb allocation had not even been made because it had not even started downloading yet. The problem is, the program, backgroundWorker1 specifically, never threw an error when it was trying to get the length of a file that did not exist yet. I assume backgroundWorker1 faulted out, causing it to prematurely go into a completed state, while backgroundWorker2 was eventually able to start the download. Unfortunately, there was nothing checking the file size of the current download anymore.

Here is the fix that I made and I have verified to work on the systems where it was not working before:


```
// This thread will handle monitoring the downloaded BioR.mdb file size
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {         
            BackgroundWorker worker = sender as BackgroundWorker; // necessary

            do // continue reporting file size until the file has finished downloading
            {
                Thread.Sleep(1000); // report once per second

                try
                {
                    long file_size = new FileInfo(@"C:\BioERemote\BioR.mdb").Length;  // get file size               
                    worker.ReportProgress(Convert.ToInt32(file_size)); // "worker" reports the file size to ProgressChanged event
                }

                catch { continue; }                                               
            } while (!is_complete); // is_complete becomes true when backgroundworker2 completes the download         
        }
```


----------



## BiggieShady (Feb 1, 2017)

Right , while you are at it, you should also avoid deadlocks:


```
private void backgroundWorker2_DoWork(object sender, DoWorkEventArgs e)
{
  try       
  {
    using (var client = new SftpClient(sftp_address, sftp_username, sftp_password))
    {
      client.Connect();
      DownloadDirectory(client, bioe_remote_source, local_remote_destination);
      client.Disconnect();
     }
   }
   finally {
     is_complete = true;
   }
}
```

although it is not really complete if exception is thrown, but without try/finally it's a deadlock when laptop is offline

Maybe even using

```
enum DowloadStatus {
 NotStarted,
 InProgress,
 Failed,
 Success
}
```

instead of bool is_complete ... to handle all phases and outcomes



dcf-joe said:


> and they immediately saw the issue . I can't believe I didn't think of it.



We were on the right track with the racing condition  but in the wrong direction - not that it completed before one second wait, but that it didn't even start inside one second wait


----------



## FordGT90Concept (Feb 1, 2017)

You got it fixed but I would only use one worker for handling the download and reporting progress.  I'm not sure how that DownloadDirectory function works so not sure how easy/difficult that will be.  I don't see why you couldn't just merge the two workers together.  Instead of using a Thread.Sleep to prevent spamming the UI, use the file size and modulus to constraint how many updates are sent.


----------



## BiggieShady (Feb 2, 2017)

FordGT90Concept said:


> I would only use one worker for handling the download and reporting progress


I suspect DownloadDirectory method blocks the calling thread and that particular FTP client library has to be used, otherwise all this "gymnastics" would make little sense because there are better ways to do it with WebClient.DownloadFileAsync which emits WebClient.DownloadProgressChanged event


----------



## dcf-joe (Feb 2, 2017)

BiggieShady said:


> I suspect DownloadDirectory method blocks the calling thread and that particular FTP client library has to be used, otherwise all this "gymnastics" would make little sense because there are better ways to do it with WebClient.DownloadFileAsync which emits WebClient.DownloadProgressChanged event



You are correct. I tried to do this all in one thread and I could never get it to work. When I have free time, I can try to perfect the code using other methods, but I just needed something to work for the time being.


----------



## trparky (Jun 15, 2017)

I know this is old but hey, better late than never.

You will need an external DLL but you can get it from NuGet, it's called SSH.NET. Once you have gotten that you can then get to work on writing your code...


```
private long longRemoteFileSize;

// This is the callback function that's used by the routine below to keep you
// updated on the download status in real time.
public void downloadStatus(ulong longBytesDownloaded)
{
    Debug.WriteLine(string.Format("{0} bytes of {1} bytes downloaded", longBytesDownloaded, longRemoteFileSize));
}

// This is the function that actually downloads the file from the remote server.
// You simply pass the sFtpClient Connection Object to this function along
// with a path to a remote file on the server and a local path on your computer.
public void downloadFile(ref Renci.SshNet.SftpClient sftpClient, string strRemotePath, string strLocalPath)
{
    IO.FileStream fileStream = new IO.FileStream(strLocalPath, IO.FileMode.Create);
    longRemoteFileSize = sftpClient.GetAttributes(strRemotePath).Size;

    // This actually downloads the file and uses the downloadStatus callback function
    // to keep you updated on the download status in real time.
    sftpClient.DownloadFile(strRemotePath, fileStream, downloadStatus);

    fileStream.Close();
    fileStream.Dispose();
}

private void Button36_Click(object sender, EventArgs e)
{
    Renci.SshNet.ConnectionInfo connectionInfo = new Renci.SshNet.ConnectionInfo("yourdomain.com", "username", new Renci.SshNet.PasswordAuthenticationMethod("username", "password"));
    Renci.SshNet.SftpClient sFTPClient = new Renci.SshNet.SftpClient(connectionInfo);

    try {
        sFTPClient.Connect();

        downloadFile(ref sFTPClient, "your/path/to/file/on/remote/server", "C:\\your\\local\\path");

        sFTPClient.Disconnect();
        sFTPClient.Dispose();

        MessageBox.Show("done.");
    } catch (Exception ex) {
        // Handle your exception here.
    }
}
```


----------

