Backgroundworker 取消worker

Backgroundworker cancel the worker

我在尝试取消 Backgroundworker 时遇到了一些麻烦。 我已经阅读了数十个 os 类似的主题,例如 How to stop BackgroundWorker correctly, How to wait correctly until BackgroundWorker completes?,但我没有到达任何地方。

我有一个 C# 应用程序,它使用 PHP WebService 将信息发送到 MySQL 数据库。如果用户出于某种原因(在表单中)单击 "back" 或 "stop" 按钮,则会触发以下代码:

BgWorkDocuments.CancelAsync();
BgWorkArticles.CancelAsync();

我知道请求是 Asynchronous,因此取消可能需要 1 或 2 秒,但它应该停止..而这根本不会发生。即使在单击 "back" 之后(当前表单是 closed 并且打开了一个新表单),后台工作人员仍在继续工作,因为我一直看到数据被插入到 MySQL.

foreach (string[] conn in lines)
 {

    string connectionString = conn[0];

    FbConnection fbConn = new FbConnection(connectionString);
    fbConn.Open();

    getDocuments(fbConn);

    // Checks if one of the backgrounds is currently busy
    // If it is, then keep pushing the events until stop.
    // Only after everything is completed is when it's allowed to close the connection.
    // 
    // OBS: Might the problem be here?
    while (BgWorkDocuments.IsBusy == true || BgWorkArticles.IsBusy == true)
    {
        Application.DoEvents();
    }

    fbConn.Close();
}

上面的代码是必需的,因为我可能有多个数据库,这就是我有循环的原因。

private void getDocuments(FbConnection fbConn)
{
    BgWorkDocuments.RunWorkerAsync();

    BgWorkDocuments.DoWork += (object _sender, DoWorkEventArgs args) =>
    {

        DataTable dt = getNewDocuments(fbConn);

        for (int i = 0; i <= dt.Rows.Count - 1; i++)
        {

            // Checks if the user has stopped the background worker
            if (BgWorkDocuments.CancellationPending == false)
            {
                // Continue doing what has to do..
                sendDocumentsToMySQL((int)dt.Rows[i]["ID"]);
            }
        }

        // After the previous loop is completed, 
        // start the new backgroundworker

        getArticles(fbConn);

    };
}

private void getArticles(FbConnection fbConn)
{
    BgWorkArticles.RunWorkerAsync();

    BgWorkArticles.DoWork += (object _sender, DoWorkEventArgs args) =>
    {

        DataTable dt = getNewArticles(fbConn);

        for (int i = 0; i <= dt.Rows.Count - 1; i++)
        {

            // Checks if the user has stopped the background worker
            if (BgWorkArticles.CancellationPending == false)
            {
                // Continue doing what has to do..
                sendArticlesToMySQL((int)dt.Rows[i]["ID"]);
            }
        }

    }; 
}

我同意对代码甚至可以工作表示惊讶的评论,因为调用 RunWorkerAsync() 与实际订阅 DoWork 事件时的顺序问题明显。此外,您对 DoEvents() 的使用是没有根据的,应该删除(就像 any 使用 DoEvents() 的情况一样)。

我还注意到,当您尝试取消时,您的工作人员实际上并没有 退出。您只是跳过处理,但继续在行上循环。如果没有看到其余代码,就不可能知道发生了什么,但是 可能 在您取消后, CancellationPending 属性 会重置为 false,允许循环再次开始做事。

缺少完整的代码示例是理解正在发生的事情的全部细节的真正障碍。

就是说,恕我直言,这似乎根本不是您实际需要 BackgroundWorker 的情况,C# 中新的 async/await 功能不需要。鉴于涉及到网络 I/O,我的猜测是每次调用 sendDocumentsToMySQL()sendArticlesToMySQL() 都可以在线程池中单独执行,而不会产生太多开销(或者甚至可以写成作为 async I/O 方法……同样,由于缺乏关于其具体实现的细节,因此无法在这方面提出任何具体建议)。鉴于此,您的代码可能会被重写,使其看起来更像这样:

private CancellationTokenSource _cancelSource;

private void stopButton_Click(object sender, EventArgs e)
{
    if (_cancelSource != null)
    {
        _cancelSource.Cancel();
    }
}

private async void startButton_Click(object sender, EventArgs e)
{
    using (CancellationTokenSource cancelSource = new CancellationTokenSource)
    {
        _cancelSource = cancelSource;

        try
        {
            foreach (string[] conn in lines)
            {
                string connectionString = conn[0];

                FbConnection fbConn = new FbConnection(connectionString);
                fbConn.Open();

                try
                {
                    await getDocuments(fbConn, cancelSource.Token);
                    await getArticles(fbConn, cancelSource.Token);
                }
                catch (OperationCanceledException)
                {
                    return;
                }
                finally
                {
                    fbConn.Close();
                }
            }
        }
        finally
        {
            _cancelSource = null;
        }
    }
}

private async Task getDocuments(FbConnection fbConn, CancellationToken cancelToken)
{
    DataTable dt = await Task.Run(() => getNewDocuments(fbConn));

    for (int i = 0; i <= dt.Rows.Count - 1; i++)
    {
        cancelToken.ThrowIfCancellationRequested();

        await Task.Run(() => sendDocumentsToMySQL((int)dt.Rows[i]["ID"]));
    }
}

private async Task getArticles(FbConnection fbConn, CancellationToken cancelToken)
{
    DataTable dt = await Task.Run(() => getNewArticles(fbConn));

    for (int i = 0; i <= dt.Rows.Count - 1; i++)
    {
        cancelToken.ThrowIfCancellationRequested();

        await Task.Run(() => sendArticlesToMySQL((int)dt.Rows[i]["ID"]));
    }
}