C# backgroundWorker 取消和调用

C# backgroundWorker cancellation and invoke

我有两个关于backgroundWorker的问题:一个是取消,一个是调用。

我的代码大致如下所示:

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;
    }
}

首先,当 DnldBgWorker 为 运行 时,我单击 StopBtn 停止了 DnldBgWorker 并且异步工作不会停止。我该如何停止DnldBgWorker?

其次,EditProcess.Text(String.Format("Downloaded: {0}\r\n"));会报错跨线程操作无效。我知道我应该派一个代表来做这件事,但我不知道具体怎么做。

++) 我的代码看起来是以非常复杂的方式完成非常简单的工作,但我在这段代码中加入了真正必要的元素,所以请理解

这里有 2 个问题:

关于取消 - 你需要在下载的循环中检查取消状态(因此只下载部分请求的文件),而不是在我不太明白的后面的循环中。

作为附加说明,您可以通过使用 WebClient.DownloadFileAsync and WebClient.CancelAsync 组合来避免使用 BackgroundWorker。

关于报告进度 - 让您 BackgroundWorker 通过 ReportProgress 向 UI 线程报告进度并从那里更新 UI。

至于如何取消线程。这是一个控制台应用程序的基本示例,我希望您可以将其放入更复杂的代码中。

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...");
}

结果如下。

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

其次,关于如何从您的线程调用与用户界面进行交互,希望这篇博客能对您有所帮助。 Updating Windows Form UI elements from another thread

如果您觉得有帮助,请告诉我。

要支持取消,您需要设置 属性

 DnldBgWorker.WorkerSupportsCancellation = true;

不清楚你是否在其他地方设置它,但你需要它来取消后台工作者,你可以在 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.

此外,我会将 GoDownload 方法更改为

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;
        }
    }
}

对于第二个问题,当您的代码在 UI 线程而不是后台线程中 运行 时,您需要调用该方法。您可以轻松实现此操作,在 ProgressChanged 事件的事件处理程序中移动文本框更新。要设置事件处理程序,您需要将另一个 属性 设置为 true

DnldBgWorker.WorkerReportsProgress = true;

并为 ProgressChanged 事件设置事件处理程序

DnldBgWorker.ProgressChanged += DnldBgWorker_ProgressChanged;

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

并使用

在 GoDownload 中引发此事件
DnldBgWorker.ReportProgress(i);

让我们在进入代码之前解决这个问题

  1. 出于某种原因,在实际下载完成后,您有一个完全冗余的循环等待取消。因此 BtnStop 不适合你
  2. 当您从在 BackgroundWorker 上下文中调用的 Dnld 调用 EditProcess.Text 时,您正在从不 "own" 它的线程访问 GUI 元素。您可以详细阅读cross-thread operation here。在您的情况下,您应该通过 ReportProgress 调用来完成。

现在你可以看到我的情况了

  1. 删除了 GoDownload 中的冗余循环,同时将 if (DnldBgWorker.CancellationPending) 检查移至下载循环。这应该使 StopBtn 现在可以工作了。
  2. 添加了 ProgressChanged 事件处理程序以在 ExecuteBtn_Click 中进行 GUI 更改。这是由 GoDownload 方法的下载循环中的 DnldBgWorker.ReportProgress 调用触发的。这里我们将自定义格式的字符串传递为 UserState
  3. 还要确保您启用了 ReportsProgressSupportsCancellation 属性,如下所示,可能在您的设计器 属性 框中或代码中 DnldBgWorker.WorkerReportsProgress = true; DnldBgWorker.WorkerSupportsCancellation = true;

希望下面的代码清楚了其他所有内容。

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;
    }
}