Search code examples
c#backgroundworkerinvokecancellation

C# backgroundWorker cancellation and invoke


I have 2 questions about backgroundWorker: one is cancellation and another is invoking.

My code briefly looks like this:

public partial class App : Form {
    //Some codes omitted
    public EditProcess Process = new EditProcess(ProcessTextBox);

    private void ExecuteBtn_Click (object sender, EventArgs e) {
        //DnldBgWorker is a backgroundWorker.
        Download Dnld = new Download(dir, Process);
        DnldBgWorker.DoWork += (obj, e) => GoDownload(Dnld, urllist, e);
        DnldBgWorker.RunWorkerAsync();
        DnldBgWorker.RunWorkerCompleted += (obj, e) => FinishExecution();
    }

    private void GoDownload(Download Dnld, string[] urllist, EventArgs e) {
        foreach(string url in urllist) {
            Dnld.Dnld(url);
        }

        for (int i = 0; i < 10; i++) {
            System.Threading.Thread.Sleep(50);
                if (DnldBgWorker.CancellationPending) {
                    e.Cancel = true;
                    return;
            }
        }
    }

    private void StopBtn_Click(object sender, EventArgs e) {
        DnldBgWorker.CancelAsync();
    }
}

public class Download {
    // Some codes omitted
    public WebClient client = new WebClient();
    public EditProcess Process;

    public Download(string dir, EditProcess Process) {
        this.dir = dir;
        this.Process = Process;
    }

    public void Dnld() {
        client.DownloadFile(url, dir);
        EditProcess.Text(String.Format("Downloaded: {0}\r\n"));
    }
}

public class EditProcess {
    public TextBox Box;

    public EditProcess(TextBox Box) {
        this.Box = Box;
    }

    public void Text(string textToAdd) {
        Box.Text += textToAdd;
    }
}

First, while DnldBgWorker is running, I clicked StopBtn to stop the DnldBgWorker and the asynchronous work would not stop. How should I stop DnldBgWorker?

Second, EditProcess.Text(String.Format("Downloaded: {0}\r\n")); would give me an error that cross-thread operation is not valid. I know that I should make a delegate to do this, but I don't know exactly how.

++) My code looks like it's doing very simple works in very complicated way, but I put really essential elements in this code so please understand


Solution

  • Let's address the issue before we get into the code

    1. For some reason, you have a completely redundant loop waiting for cancel after your actual download is done. Hence BtnStop does not work for you
    2. When you call EditProcess.Text from Dnld which is invoked in the BackgroundWorker context, you are accessing a GUI element from a thread which does not "own" it. You can read in detail about cross-thread operation here. In your case, you should do it via your ReportProgress call.

    Now you can see how I have

    1. Removed the redundant loop from GoDownload while moving the if (DnldBgWorker.CancellationPending) check to the download loop. This should make the StopBtn work now.
    2. Added the ProgressChanged event handler to do the GUI change in the ExecuteBtn_Click. This is triggered by DnldBgWorker.ReportProgress call in the download loop of GoDownload method. Here we pass the custom formatted string as UserState
    3. Also make sure that you have the enabled the ReportsProgress and SupportsCancellation properties like below, perhaps in your designer property box or in code lile DnldBgWorker.WorkerReportsProgress = true; DnldBgWorker.WorkerSupportsCancellation = true;

    Hope everything else is clear with the code below.

    public partial class App : Form {
        //Some codes omitted
        public EditProcess Process = new EditProcess(ProcessTextBox);
    
        private void ExecuteBtn_Click (object sender, EventArgs e) {
            //DnldBgWorker is a backgroundWorker.
            Download Dnld = new Download(dir, Process);
            DnldBgWorker.DoWork += (obj, e) => GoDownload(Dnld, urllist, e);
            DnldBgWorker.RunWorkerAsync();
            DnldBgWorker.RunWorkerCompleted += (obj, e) => FinishExecution();
            DnldBgWorker.ProgressChanged += (s, e) => EditProcess.Text((string)e.UserState);;
        }
    
        private void GoDownload(Download Dnld, string[] urllist, EventArgs e) {
            foreach(string url in urllist) {
                Dnld.Dnld(url);
                DnldBgWorker.ReportProgress(0, String.Format($"Downloaded: {url}\r\n"));
                if (DnldBgWorker.CancellationPending) {
                    e.Cancel = true;
                    return;
                }
            }
        }
    
        private void StopBtn_Click(object sender, EventArgs e) {
            DnldBgWorker.CancelAsync();
        }
    }
    
    public class Download {
        // Some codes omitted
        public WebClient client = new WebClient();
        public EditProcess Process;
    
        public Download(string dir, EditProcess Process) {
            this.dir = dir;
            this.Process = Process;
        }
    
        public void Dnld() {
            client.DownloadFile(url, dir);
        }
    }
    
    public class EditProcess {
        public TextBox Box;
    
        public EditProcess(TextBox Box) {
            this.Box = Box;
        }
    
        public void Text(string textToAdd) {
            Box.Text += textToAdd;
        }
    }