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);
让我们在进入代码之前解决这个问题
- 出于某种原因,在实际下载完成后,您有一个完全冗余的循环等待取消。因此
BtnStop
不适合你
- 当您从在 BackgroundWorker 上下文中调用的
Dnld
调用 EditProcess.Text
时,您正在从不 "own" 它的线程访问 GUI 元素。您可以详细阅读cross-thread operation here。在您的情况下,您应该通过 ReportProgress
调用来完成。
现在你可以看到我的情况了
- 删除了
GoDownload
中的冗余循环,同时将 if (DnldBgWorker.CancellationPending)
检查移至下载循环。这应该使 StopBtn
现在可以工作了。
- 添加了
ProgressChanged
事件处理程序以在 ExecuteBtn_Click
中进行 GUI 更改。这是由 GoDownload
方法的下载循环中的 DnldBgWorker.ReportProgress
调用触发的。这里我们将自定义格式的字符串传递为 UserState
- 还要确保您启用了
ReportsProgress
和 SupportsCancellation
属性,如下所示,可能在您的设计器 属性 框中或代码中 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;
}
}
我有两个关于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);
让我们在进入代码之前解决这个问题
- 出于某种原因,在实际下载完成后,您有一个完全冗余的循环等待取消。因此
BtnStop
不适合你 - 当您从在 BackgroundWorker 上下文中调用的
Dnld
调用EditProcess.Text
时,您正在从不 "own" 它的线程访问 GUI 元素。您可以详细阅读cross-thread operation here。在您的情况下,您应该通过ReportProgress
调用来完成。
现在你可以看到我的情况了
- 删除了
GoDownload
中的冗余循环,同时将if (DnldBgWorker.CancellationPending)
检查移至下载循环。这应该使StopBtn
现在可以工作了。 - 添加了
ProgressChanged
事件处理程序以在ExecuteBtn_Click
中进行 GUI 更改。这是由GoDownload
方法的下载循环中的DnldBgWorker.ReportProgress
调用触发的。这里我们将自定义格式的字符串传递为UserState
- 还要确保您启用了
ReportsProgress
和SupportsCancellation
属性,如下所示,可能在您的设计器 属性 框中或代码中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;
}
}