• Welcome to TechPowerUp Forums, Guest! Please check out our forum guidelines for info related to our community.

C# backgroundWorker Question

Joined
Feb 11, 2008
Messages
612 (0.10/day)
Location
DFW, Texas, USA
System Name Built By Me
Processor AMD 9800X3D
Motherboard Gigabyte X870 Aorus Elite WiFi7
Cooling Water Cooling - CPU Only - Heatkiller IV Pro - TG PhaseSheet PTM
Memory 32GB (2 x 16) - GSkill FlareX5 DDR5 6000 Mhz
Video Card(s) RTX 4080 - ASUS ROG Strix 16GB OC - P Mode
Storage 2TB Inland Performance Plus NVMe
Display(s) Alienware AW2723DF @ 280 Hz @ 1440P
Case Fractal Design Define S2
Audio Device(s) Corsair Virtuoso Pro Headset
Power Supply 1000W MSI MPG A1000G PCIE5
Mouse Razer Viper V3 Pro @ 2k Hz
Keyboard Asus ROG Strix Scope II 96 Wireless - ROG NX Snow Switches
Software Windows 11 Pro
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

The Exiled Airman
Joined
Jul 2, 2007
Messages
44,274 (6.80/day)
Location
Republic of Texas (True Patriot)
System Name PCGOD
Processor AMD FX 8350@ 5.0GHz
Motherboard Asus TUF 990FX Sabertooth R2 2901 Bios
Cooling Scythe Ashura, 2×BitFenix 230mm Spectre Pro LED (Blue,Green), 2x BitFenix 140mm Spectre Pro LED
Memory 16 GB Gskill Ripjaws X 2133 (2400 OC, 10-10-12-20-20, 1T, 1.65V)
Video Card(s) AMD Radeon 290 Sapphire Vapor-X
Storage Samsung 840 Pro 256GB, WD Velociraptor 1TB
Display(s) NEC Multisync LCD 1700V (Display Port Adapter)
Case AeroCool Xpredator Evil Blue Edition
Audio Device(s) Creative Labs Sound Blaster ZxR
Power Supply Seasonic 1250 XM2 Series (XP3)
Mouse Roccat Kone XTD
Keyboard Roccat Ryos MK Pro
Software Windows 7 Pro 64
Joined
Feb 8, 2012
Messages
3,014 (0.62/day)
Location
Zagreb, Croatia
System Name Windows 10 64-bit Core i7 6700
Processor Intel Core i7 6700
Motherboard Asus Z170M-PLUS
Cooling Corsair AIO
Memory 2 x 8 GB Kingston DDR4 2666
Video Card(s) Gigabyte NVIDIA GeForce GTX 1060 6GB
Storage Western Digital Caviar Blue 1 TB, Seagate Baracuda 1 TB
Display(s) Dell P2414H
Case Corsair Carbide Air 540
Audio Device(s) Realtek HD Audio
Power Supply Corsair TX v2 650W
Mouse Steelseries Sensei
Keyboard CM Storm Quickfire Pro, Cherry MX Reds
Software MS Windows 10 Pro 64-bit
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
 
Joined
Feb 11, 2008
Messages
612 (0.10/day)
Location
DFW, Texas, USA
System Name Built By Me
Processor AMD 9800X3D
Motherboard Gigabyte X870 Aorus Elite WiFi7
Cooling Water Cooling - CPU Only - Heatkiller IV Pro - TG PhaseSheet PTM
Memory 32GB (2 x 16) - GSkill FlareX5 DDR5 6000 Mhz
Video Card(s) RTX 4080 - ASUS ROG Strix 16GB OC - P Mode
Storage 2TB Inland Performance Plus NVMe
Display(s) Alienware AW2723DF @ 280 Hz @ 1440P
Case Fractal Design Define S2
Audio Device(s) Corsair Virtuoso Pro Headset
Power Supply 1000W MSI MPG A1000G PCIE5
Mouse Razer Viper V3 Pro @ 2k Hz
Keyboard Asus ROG Strix Scope II 96 Wireless - ROG NX Snow Switches
Software Windows 11 Pro
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.

Code:
 // 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.";
        }
 
Joined
Feb 8, 2012
Messages
3,014 (0.62/day)
Location
Zagreb, Croatia
System Name Windows 10 64-bit Core i7 6700
Processor Intel Core i7 6700
Motherboard Asus Z170M-PLUS
Cooling Corsair AIO
Memory 2 x 8 GB Kingston DDR4 2666
Video Card(s) Gigabyte NVIDIA GeForce GTX 1060 6GB
Storage Western Digital Caviar Blue 1 TB, Seagate Baracuda 1 TB
Display(s) Dell P2414H
Case Corsair Carbide Air 540
Audio Device(s) Realtek HD Audio
Power Supply Corsair TX v2 650W
Mouse Steelseries Sensei
Keyboard CM Storm Quickfire Pro, Cherry MX Reds
Software MS Windows 10 Pro 64-bit
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
 
Last edited:
Joined
Feb 11, 2008
Messages
612 (0.10/day)
Location
DFW, Texas, USA
System Name Built By Me
Processor AMD 9800X3D
Motherboard Gigabyte X870 Aorus Elite WiFi7
Cooling Water Cooling - CPU Only - Heatkiller IV Pro - TG PhaseSheet PTM
Memory 32GB (2 x 16) - GSkill FlareX5 DDR5 6000 Mhz
Video Card(s) RTX 4080 - ASUS ROG Strix 16GB OC - P Mode
Storage 2TB Inland Performance Plus NVMe
Display(s) Alienware AW2723DF @ 280 Hz @ 1440P
Case Fractal Design Define S2
Audio Device(s) Corsair Virtuoso Pro Headset
Power Supply 1000W MSI MPG A1000G PCIE5
Mouse Razer Viper V3 Pro @ 2k Hz
Keyboard Asus ROG Strix Scope II 96 Wireless - ROG NX Snow Switches
Software Windows 11 Pro
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:

Code:
// 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         
        }
 
Joined
Feb 8, 2012
Messages
3,014 (0.62/day)
Location
Zagreb, Croatia
System Name Windows 10 64-bit Core i7 6700
Processor Intel Core i7 6700
Motherboard Asus Z170M-PLUS
Cooling Corsair AIO
Memory 2 x 8 GB Kingston DDR4 2666
Video Card(s) Gigabyte NVIDIA GeForce GTX 1060 6GB
Storage Western Digital Caviar Blue 1 TB, Seagate Baracuda 1 TB
Display(s) Dell P2414H
Case Corsair Carbide Air 540
Audio Device(s) Realtek HD Audio
Power Supply Corsair TX v2 650W
Mouse Steelseries Sensei
Keyboard CM Storm Quickfire Pro, Cherry MX Reds
Software MS Windows 10 Pro 64-bit
Right :toast:, while you are at it, you should also avoid deadlocks:

Code:
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
Code:
enum DowloadStatus {
 NotStarted,
 InProgress,
 Failed,
 Success
}

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

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 :laugh: but in the wrong direction - not that it completed before one second wait, but that it didn't even start inside one second wait
 
Last edited:

FordGT90Concept

"I go fast!1!11!1!"
Joined
Oct 13, 2008
Messages
26,263 (4.35/day)
Location
IA, USA
System Name BY-2021
Processor AMD Ryzen 7 5800X (65w eco profile)
Motherboard MSI B550 Gaming Plus
Cooling Scythe Mugen (rev 5)
Memory 2 x Kingston HyperX DDR4-3200 32 GiB
Video Card(s) AMD Radeon RX 7900 XT
Storage Samsung 980 Pro, Seagate Exos X20 TB 7200 RPM
Display(s) Nixeus NX-EDG274K (3840x2160@144 DP) + Samsung SyncMaster 906BW (1440x900@60 HDMI-DVI)
Case Coolermaster HAF 932 w/ USB 3.0 5.25" bay + USB 3.2 (A+C) 3.5" bay
Audio Device(s) Realtek ALC1150, Micca OriGen+
Power Supply Enermax Platimax 850w
Mouse Nixeus REVEL-X
Keyboard Tesoro Excalibur
Software Windows 10 Home 64-bit
Benchmark Scores Faster than the tortoise; slower than the hare.
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.
 
Joined
Feb 8, 2012
Messages
3,014 (0.62/day)
Location
Zagreb, Croatia
System Name Windows 10 64-bit Core i7 6700
Processor Intel Core i7 6700
Motherboard Asus Z170M-PLUS
Cooling Corsair AIO
Memory 2 x 8 GB Kingston DDR4 2666
Video Card(s) Gigabyte NVIDIA GeForce GTX 1060 6GB
Storage Western Digital Caviar Blue 1 TB, Seagate Baracuda 1 TB
Display(s) Dell P2414H
Case Corsair Carbide Air 540
Audio Device(s) Realtek HD Audio
Power Supply Corsair TX v2 650W
Mouse Steelseries Sensei
Keyboard CM Storm Quickfire Pro, Cherry MX Reds
Software MS Windows 10 Pro 64-bit
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
 
Joined
Feb 11, 2008
Messages
612 (0.10/day)
Location
DFW, Texas, USA
System Name Built By Me
Processor AMD 9800X3D
Motherboard Gigabyte X870 Aorus Elite WiFi7
Cooling Water Cooling - CPU Only - Heatkiller IV Pro - TG PhaseSheet PTM
Memory 32GB (2 x 16) - GSkill FlareX5 DDR5 6000 Mhz
Video Card(s) RTX 4080 - ASUS ROG Strix 16GB OC - P Mode
Storage 2TB Inland Performance Plus NVMe
Display(s) Alienware AW2723DF @ 280 Hz @ 1440P
Case Fractal Design Define S2
Audio Device(s) Corsair Virtuoso Pro Headset
Power Supply 1000W MSI MPG A1000G PCIE5
Mouse Razer Viper V3 Pro @ 2k Hz
Keyboard Asus ROG Strix Scope II 96 Wireless - ROG NX Snow Switches
Software Windows 11 Pro
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.
 
Joined
Mar 6, 2017
Messages
3,385 (1.14/day)
Location
North East Ohio, USA
System Name My Ryzen 7 7700X Super Computer
Processor AMD Ryzen 7 7700X
Motherboard Gigabyte B650 Aorus Elite AX
Cooling DeepCool AK620 with Arctic Silver 5
Memory 2x16GB G.Skill Trident Z5 NEO DDR5 EXPO (CL30)
Video Card(s) XFX AMD Radeon RX 7900 GRE
Storage Samsung 980 EVO 1 TB NVMe SSD (System Drive), Samsung 970 EVO 500 GB NVMe SSD (Game Drive)
Display(s) Acer Nitro XV272U (DisplayPort) and Acer Nitro XV270U (DisplayPort)
Case Lian Li LANCOOL II MESH C
Audio Device(s) On-Board Sound / Sony WH-XB910N Bluetooth Headphones
Power Supply MSI A850GF
Mouse Logitech M705
Keyboard Steelseries
Software Windows 11 Pro 64-bit
Benchmark Scores https://valid.x86.fr/liwjs3
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...

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.
    }
}
 
Last edited:
Top