0

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

Vikhram
  • 4,294
  • 1
  • 20
  • 32
Charlie Lee
  • 91
  • 1
  • 5
  • What happen if you increment the time you put your thread to sleep? Instead of 50 try with 1000. – Steve Oct 06 '17 at 17:22

4 Answers4

0

There are 2 issues here:

Regarding cancellation - you need to check for cancellation status in the loop that does downloading (thus downloading only part of requested files), not in the later loop which I don't really understand.

As an additional side note you can avoid using BackgroundWorker by using WebClient.DownloadFileAsync and WebClient.CancelAsync combo.

As of reporting progress - make you BackgroundWorker report progress back to the UI thread via ReportProgress and update UI from there.

orhtej2
  • 2,133
  • 3
  • 16
  • 26
0

As for how to cancel a thread. Here is a basic example, for a console application, that I hope you can fit into your more complex code.

void Main()
{
    var tokenSource = new CancellationTokenSource();
    System.Threading.Tasks.Task.Run(() => BackgroundThread(tokenSource.Token));

    Thread.Sleep(5000);
    tokenSource.Cancel();   
}

private void BackgroundThread(CancellationToken token)
{
    while (token.IsCancellationRequested == false) {
        Console.Write(".");
        Thread.Sleep(1000);
    }

    Console.WriteLine("\nCancellation Requested Thread Exiting...");
}

The results would be the following.

.....
Cancellation Requested Thread Exiting...

Secondly, as far as how to invoke from your thread to interact with the user interface, hopefully this blog will help you. Updating Windows Form UI elements from another thread

Please let me know if you found this helpful.

dacke.geo
  • 233
  • 2
  • 13
0

To support cancellation you need to set the property

 DnldBgWorker.WorkerSupportsCancellation = true;

It is not clear if you set it somewhere else, but you need it to cancel the background worker as you can read on MSDN

Set the WorkerSupportsCancellation property to true if you want the BackgroundWorker to support cancellation. When this property is true, you can call the CancelAsync method to interrupt a background operation.

Also I would change the GoDownload method to

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

        // this is just to give more time to test the cancellation
        System.Threading.Thread.Sleep(500);

        // Check the cancellation after each download
        if (DnldBgWorker.CancellationPending) 
        {
            e.Cancel = true;
            return;
        }
    }
}

For the second problem you need to call that method when your code is running on the UI thread and not in the background thread. You could easily achieve this moving the textbox update in the event handler for the ProgressChanged event. To set up the event handler you need another property set to true

DnldBgWorker.WorkerReportsProgress = true;

And set the event handler for the ProgressChanged event

DnldBgWorker.ProgressChanged += DnldBgWorker_ProgressChanged;

private void DnldBgWorker_ProgressChanged(object sender,    ProgressChangedEventArgs e)
{
    EditProcess.Text(String.Format("Downloaded: {0}\r\n", e.ProgressPercentage));
}

and raise this event in the GoDownload with

DnldBgWorker.ReportProgress(i);
Steve
  • 213,761
  • 22
  • 232
  • 286
0

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;
    }
}
Vikhram
  • 4,294
  • 1
  • 20
  • 32
  • Thanks for perfect solution, still two problems exist. Actually the class `EditProcess` has more methods than just `Text`. I have `ShowPartBar` and `ShowTotalBar` to use. Also `EditProcess` has 3 elements, so how should I report progress? – Charlie Lee Oct 07 '17 at 02:06
  • Oh I solved the problem using arrays. Thanks for great solution! – Charlie Lee Oct 07 '17 at 02:26