【问题标题】:C# backgroundWorker cancellation and invokeC# backgroundWorker 取消和调用
【发布时间】:2018-03-18 13:27:06
【问题描述】:

我有两个关于 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")); 会给我一个错误,即跨线程操作无效。我知道我应该派一个代表来做这件事,但我不知道具体怎么做。

++) 我的代码看起来以非常复杂的方式做着非常简单的工作,但我在这段代码中放入了非常重要的元素,所以请理解

【问题讨论】:

  • 如果增加让线程休眠的时间会发生什么?而不是 50 尝试 1000。

标签: c# backgroundworker invoke cancellation


【解决方案1】:

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

  1. 由于某种原因,在实际下载完成后,您有一个完全冗余的循环等待取消。因此BtnStop 不适合你
  2. 当您从在 BackgroundWorker 上下文中调用的 Dnld 调用 EditProcess.Text 时,您正在从不“拥有”它的线程访问 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;
    }
}

【讨论】:

  • 感谢完美解决,仍然存在两个问题。实际上EditProcess 类有更多的方法,而不仅仅是Text。我有ShowPartBarShowTotalBar 可以使用。 EditProcess 也有 3 个元素,我应该如何报告进度?
  • 哦,我用数组解决了这个问题。感谢您的出色解决方案!
【解决方案2】:

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

 DnldBgWorker.WorkerSupportsCancellation = true;

不清楚是否将其设置在其他地方,但您需要它来取消后台工作程序,如您可以在 MSDN 上阅读的那样

如果需要,请将 WorkerSupportsCancellation 属性设置为 true BackgroundWorker 支持取消。当这个属性为真时, 您可以调用 CancelAsync 方法来中断背景 操作。

我还将 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);

【讨论】:

    【解决方案3】:

    关于如何取消一个线程。这是一个控制台应用程序的基本示例,希望您能适应更复杂的代码。

    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

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

    【讨论】:

      【解决方案4】:

      这里有两个问题:

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

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

      在报告进度时 - 让您通过 BackgroundWorker 将进度报告回 UI 线程,然后通过 ReportProgress 更新 UI。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-01-21
        • 1970-01-01
        • 1970-01-01
        • 2011-03-14
        • 2015-04-17
        • 1970-01-01
        • 2018-06-25
        • 1970-01-01
        相关资源
        最近更新 更多