Отмена BackgroundWorker


Я использую BackgroundWorker в своем приложении winforms для выполнения длительной задачи, которая выполняется в другом классе (выполнение операций с базой данных). Поскольку вся работа выполняется в другом классе, отмена не так проста. Я использую событие в другом классе (GenerateStats) для обновления хода выполнения по мере завершения фоновой операции. Я хочу сделать то же самое с отменой операции. Я не могу просто вызвать cancellationPending в функции DoWork, потому что метод никогда не увидит его до тех пор, пока он заканчивает, и это разрушает цель. Я хочу функциональность отмены без передачи BackgroundWorker в generateForSubject(). Существует ли вообще, что это может поддерживать отмену из метода generateForSubject() в классе GenerateStats. Это экземпляр класса, в котором выполняется операция:

GenerateStats genStats = new GenerateStats();

Это моя функция DoWork, которая вызывает метод ReportProgress всякий раз, когда вызывается событие в другом классе. Он также вызывает метод из другого класса generateForSubject(), который выполняет оперативный.

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;

    genStats.ProgressChanged += (s, pe) => worker.ReportProgress(pe.ProgressPercentage, pe.UserState);
    genStats.generateForSubject();
}

Это обработчик событий нажатия кнопки, который должен инициировать отмену и запустить CancelAsync()

private void btnStop_Click(object sender, EventArgs e)
{
    if (backgroundWorker.IsBusy)
    {
        backgroundWorker.CancelAsync();
    }
}

Это мой отдельный класс, который выполняет операцию, а также создает обработчик событий ProgressChanged, чтобы мой класс мог обновить форму с информацией о ходе выполнения. Было бы здорово, если бы отмена могла действовать аналогичным образом.

public event ProgressChangedEventHandler ProgressChanged;

protected virtual void OnProgressChanged(int progress, string message)
{
    if (ProgressChanged != null)
    {
        ProgressChanged(this, new ProgressChangedEventArgs(progress, message));
    }
}

public void generateForSubject()
{
    //Perform db operation not important, but it takes time

    OnProgressChanged(33, "Finished 1st set of stats");
    //I hope to check for cancellation here

    //Perform db operation not important, but it takes time

    OnProgressChanged(66, "Finished 2nd set of stats");
    //I hope to check for cancellation here        

    //Perform db operation not important, but it takes time

    OnProgressChanged(99, "Finished 3rd set of stats");
    //I hope to check for cancellation here
}
Просто чтобы прояснить, есть ли какая-то неопределенность относительно того, о чем я спрашиваю, есть ли какой-либо способ для меня поддержать отмена моего backgroundWorker в другом классе без передачи backgroundWorker методу. Если нет абсолютно никакого способа, и я должен, то я передам backgroundWorker
2 2

2 ответа:

Было бы полезно, если бы вы могли более конкретно рассказать о своем нежелании передавать экземпляр BackgroundWorker. Знание того, почему это требование к дизайну, может помочь лучше ответить на ваш вопрос.

Тем не менее, вы можете применить ту же философию, что и для события ProgressChanged, и делегировать проверку отмены. Например:
class GenerateStats
{
    public event EventHandler<CancelEventArgs> CheckCancel;

    private bool OnCheckCancel()
    {
        EventHandler<CancelEventArgs> handler = CheckCancel;

        if (handler != null)
        {
            CancelEventArgs e = new CancelEventArgs();

            handler(this, e);

            return e.Cancel;
        }

        return false;
    }

    public void generateForSubject()
    {
        //Perform db operation not important, but it takes time

        OnProgressChanged(33, "Finished 1st set of stats");
        if (OnCheckCancel())
        {
            // Or other cancellation logic here
            return;
        }

        //Perform db operation not important, but it takes time

        OnProgressChanged(66, "Finished 2nd set of stats");
        if (OnCheckCancel())
        {
            // Or other cancellation logic here
            return;
        }

        //Perform db operation not important, but it takes time

        OnProgressChanged(99, "Finished 3rd set of stats");
        if (OnCheckCancel())
        {
            // Or other cancellation logic here
            return;
        }
    }
}

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;

    genStats.ProgressChanged += (s, pe) => worker.ReportProgress(pe.ProgressPercentage, pe.UserState);
    genStats.CheckCancel += (sender1, e1) => e1.Cancel = worker.CancellationPending;
    genStats.generateForSubject();
}

Это позволяет классу GenerateStats проверять ожидание отмены, не имея прямого доступа к экземпляру BackgroundWorker, как и событие ProgressChanged. позволяет ему сообщать о прогрессе через BackgroundWorker без прямого доступа к BackgroundWorker.

Ваш метод должен периодически проверять свойство CancellationPending, чтобы увидеть, был ли сделан запрос на отмену. А затем принять решение об отмене операции. Смотрите MSDN link для этого.

Из MSDN:

Имейте в виду, что ваш код в обработчике событий DoWork может завершить свою работу. работа как запрос на отмену производится, и ваш цикл опроса может пропустить отмену, если установить значение true. В этом случае Отмененный флаг Система.ComponentModel.RunWorkerCompletedEventArgs в обработчик событий RunWorkerCompleted не будет установлен в значение true, даже если хотя запрос на отмену был сделан. Эта ситуация называется состояние гонки и является общей проблемой в многопоточном программировании. Дополнительные сведения о проблемах многопоточного проектирования см. В разделе Управление Пронизывание Лучших Практик.

Редактировать:

Если вы не хотите передавать backgroundworker в метод generateForSubject. Установить свойство в классе при получении CancelAsync. И перед каждой операцией проверяйте значение этого свойства из метода generateForSubject.