【问题标题】:BackgroundWorker questions on cancellationBackgroundWorker 关于取消的问题
【发布时间】:2014-01-06 09:08:56
【问题描述】:

我正在尝试(再次)在我的应用中实现 Backgroundworker,以便 UI 能够响应。用户选择要处理的文件,这些文件最终在网格上。处理开始时是这样的:

for (int i = 0; i<Grid.Rows.Count; i++)
{

    Worker work = new Worker();
    //set up data for processing based on the current row in the grid
    bw.RunWorkerAsync(work);
    addStatusToGrid();
    //clean up; indicate work on this file is done
    work=null;


}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
    Worker work = (Worker)e.Argument;

    if (work.doTheWork() == false)
    {
        //indicate failure

    }
    else
    {
        //indicate success
    }
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    //not sure what to do here
}

但是发生的情况是,一旦调用 bw.RunWorkerAsync(),应用程序就会立即转到 for 循环中的下一行,然后再次启动相同的进程。现在,我有点想要这个,但我收到一条错误消息,“这个后台工作人员目前很忙,不能同时运行多个任务”。如果我只处理一个项目,addStatusToGrid() 会立即被调用,当然没有状态要添加,因为工作还没有完成。

几个问题:如果我可以使用这种机制一次启动多个处理会话,那将是一件好事。但是如何防止 addStatusToGrid() 立即被调用?

现在,用户界面确实更新了很多。我有一个进度条,它经常显示更新。我猜我无法使用取消按钮取消操作,因为 UI 线程很忙。

【问题讨论】:

  • 这种方式需要处理多少行?
  • 简而言之,您应该在完成的事件中更新网格。即启动后台作业。当每一个完成时,结果就会显示出来。
  • 我已经编辑了你的标题。请参阅“Should questions include “tags” in their titles?”,其中的共识是“不,他们不应该”。
  • 有什么理由不想使用任务类而不是后台工作者?您似乎没有向 UI 提供状态更新(使用 bw 很容易做到)。正如下面的答案所述,数据/网格的大小和所需的处理时间将决定何时更新 UI 的最佳方法。

标签: c# backgroundworker


【解决方案1】:

从您的问题看来,您在网格中有多行需要处理。现在后台工作人员在另一个线程中执行该方法,从而释放 UI 以执行其他操作。 (IE 状态更新)。由于这是在另一个线程上执行的,因此 UI 可以正常继续处理。 UI 线程不会等待 BackgroundWorker 完成。如果它确实等待,则使用后台工作人员将毫无用处。此外,BackgroundWorker 必须在开始另一个操作之前完成操作(因此您的错误消息)。

现在很难理解你在 work.doTheWork() 中所做的事情,但我怀疑你会想在这里采取另一种方法。

在我们开始解决这个问题之前,您必须了解的第一件事是后台工作人员的取消实际上并没有取消线程,而是为您的代码提供了一个“标志”来检查 BackgroundWorker 是否正在等待计算 (MSDN on this flag )。

向前迈进。由于 Grid 保存在 UI 线程上,因此您需要了解我们想要捕获的网格中的数据并将其传递到 BackgroundWorker 上下文中。正如这个 inst 提供的,我将做一个非常基本的例子。现在您可以运行多个后台工作人员来单独处理每一行,从而在网格中每行生成 1 个工作人员。对于您的要求,这可能是理想的,但对于较大的网格,您实际上是在每行创建 1 个线程来处理,而在具有数百行的网格中,这可能是灾难性的。

因此,您可以创建如下所示的方法。我已经评论了代码以帮助您完成它。基本上,这显示了取消工作人员、报告进度和单独遍历每个行项目的能力。我还添加了一些基本类以供在工作人员内部使用。 [注意这只是演示代码]

class DoWorkTester
{
    int currentIndex = 0;
    GridRow[] rows;


    public void ExecuteWorkers()
    {

        GridRow rowA = new GridRow
        {
            PropertyA = "abc",
            PropertyB = "def"
        };

        GridRow rowB = new GridRow
        {
            PropertyA = "123",
            PropertyB = "456"
        };

        GridRow rowC = new GridRow
        {
            PropertyA = "xyz",
            PropertyB = "789"
        };

        rows = new GridRow[] { rowA, rowB, rowC };

        currentIndex = 0;

        runWorkers();
    }

    BackgroundWorker worker;

    void runWorkers()
    {
        //done all rows
        if (currentIndex >= rows.Length - 1)
            return;

        //is the worker busy?
        if (worker != null && worker.IsBusy)
        {
            //TODO: Trying to execute on a running worker.
            return;
        }

        //create a new worker
        worker = new BackgroundWorker();
        worker.WorkerSupportsCancellation = true;
        worker.WorkerReportsProgress = true;
        worker.ProgressChanged += (o, e) =>
        {
            //TODO: Update the UI that the progress has changed
        };
        worker.DoWork += (o, e) =>
        {
            if (currentIndex >= rows.Length - 1)
            {
                //indicate done
                e.Result = new WorkResult
                {
                    Message = "",
                    Status = WorkerResultStatus.DONE
                };
                return;
            }
            //check if the worker is cancelling
            else if (worker.CancellationPending)
            {
                e.Result = WorkResult.Cancelled;
                return;
            }
            currentIndex++;

            //report the progress to the UI thread.
            worker.ReportProgress(currentIndex);

            //TODO: Execute your logic.
            if (worker.CancellationPending)
            {
                e.Result = WorkResult.Cancelled;
                return;
            }

            e.Result = WorkResult.Completed;
        };
        worker.RunWorkerCompleted += (o, e) =>
        {
            var result = e.Result as WorkResult;
            if (result == null || result.Status != WorkerResultStatus.DONE)
            {
                //TODO: Code for cancelled  \ failed results
                worker.Dispose();
                worker = null;
                return;
            }
            //Re-call the run workers thread
            runWorkers();
        };
        worker.RunWorkerAsync(rows[currentIndex]);
    }

    /// <summary>
    /// cancel the worker
    /// </summary>
    void cancelWorker()
    {
        //is the worker set to an instance and is it busy?
        if (worker != null && worker.IsBusy)
            worker.CancelAsync();

    }
}

enum WorkerResultStatus
{
    DONE,
    CANCELLED,
    FAILED
}

class WorkResult
{
    public string Message { get; set; }
    public WorkerResultStatus Status { get; set; }

    public static WorkResult Completed
    {
        get
        {
            return new WorkResult
            {
                Status = WorkerResultStatus.DONE,
                Message = ""
            };
        }
    }

    public static WorkResult Cancelled
    {
        get
        {
            return new WorkResult
            {
                Message = "Cancelled by user",
                Status = WorkerResultStatus.CANCELLED
            };
        }
    }
}


class GridRow
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
}

现在,如果您想一次处理多行,则必须调整代码以使用某种 Worker Pooling 或将所有行数据传递给第一个后台 Worker,从而消除递归。

干杯。

【讨论】:

  • 这不仅需要对逻辑进行小的更改。是的,我会处理多行。我可以将行数据封装为对象并传递它们的数组;不会对内存造成太大影响。我必须确保如果产生多个线程,它们会以某种方式受到限制,因为可能有 1000 行。不太可能但可能。产生比核心更多的线程没有意义,因为它们只会等待。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-03-23
  • 2015-01-21
  • 1970-01-01
  • 2011-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多