Перевернутый фоновый рабочий - PullRequest
2 голосов
/ 02 августа 2011

У меня есть несколько классов, которые делают вещи, обычно перебирают набор записей и вызывают веб-сервис или два для каждой записи.

На данный момент все это работает в потоке графического интерфейса и вешает рисование. Первой мыслью было использовать BackgroundWorker и реализовать красивый индикатор выполнения, обрабатывать ошибки, завершение и т. Д. Все приятные вещи, которые позволяет фоновый работник.

Как только код попал на экран, он начал пахнуть. Я писал много фонового работника в каждый класс, повторяя большую часть метода ProcessRows в методе bw_DoWork и думая, что должен быть лучший способ, и это, вероятно, уже сделано.

Прежде чем я продолжу изобретать колесо, есть ли шаблон или реализация для класса, который отделяет фонового работника? Для этого потребуются классы, которые реализуют интерфейс, такой как ibackgroundable, но классы все еще могут работать автономно, и для реализации интерфейса потребуются минимальные изменения.

Редактировать: упрощенный пример, запрошенный @Henk:

У меня есть:

    private void buttonUnlockCalls_Click(object sender, EventArgs e)
    {
        UnlockCalls unlockCalls = new UnlockCalls();
        unlockCalls.MaxRowsToProcess = 1000;
        int processedRows = unlockCalls.ProcessRows();
        this.textProcessedRows.text = processedRows.ToString();
    }

Я думаю, что хочу:

    private void buttonUnlockCalls_Click(object sender, EventArgs e)
    {
        UnlockCalls unlockCalls = new UnlockCalls();
        unlockCalls.MaxRowsToProcess = 1000;

        PushToBackground pushToBackground = new PushToBackground(unlockCalls)
        pushToBackground.GetReturnValue = pushToBackground_GetReturnValue;
        pushToBackground.DoWork();
    }

    private void pushToBackground_GetReturnValue(object sender, EventArgs e)
    {
        int processedRows = e.processedRows;
        this.textProcessedRows.text = processedRows.ToString();
    }

Я мог бы пойти дальше и сделать это, но не хочу изобретать заново.

Ответ, который я ищу, будет выглядеть следующим образом: «Да, Джо сделал хорошую реализацию этого (здесь)» или «Это шаблон прокси-виджета, прочитайте об этом (здесь)»

Ответы [ 2 ]

1 голос
/ 11 августа 2011

Каждая операция должна реализовывать следующий интерфейс:

/// <summary>
/// Allows progress to be monitored on a multi step operation
/// </summary>
interface ISteppedOperation
{
    /// <summary>
    /// Move to the next item to be processed.
    /// </summary>
    /// <returns>False if no more items</returns>
    bool MoveNext();

    /// <summary>
    /// Processes the current item
    /// </summary>
    void ProcessCurrent();

    int StepCount { get; }
    int CurrentStep { get; }
}

Это отделяет перечисление шагов от обработки.

Вот пример операции:

class SampleOperation : ISteppedOperation
{
    private int maxSteps = 100;

    //// The basic way of doing work that I want to monitor
    //public void DoSteppedWork()
    //{
    //    for (int currentStep = 0; currentStep < maxSteps; currentStep++)
    //    {
    //        System.Threading.Thread.Sleep(100);
    //    }
    //}

    // The same thing broken down to implement ISteppedOperation
    private int currentStep = 0; // before the first step
    public bool MoveNext()
    {
        if (currentStep == maxSteps)
            return false;
        else
        {
            currentStep++;
            return true;
        }
    }

    public void ProcessCurrent()
    {
        System.Threading.Thread.Sleep(100);
    }

    public int StepCount
    {
        get { return maxSteps; }
    }

    public int CurrentStep
    {
        get { return currentStep; }
    }

    // Re-implement the original method so it can still be run synchronously
    public void DoSteppedWork()
    {
        while (MoveNext())
            ProcessCurrent();
    }
}

Это можно вызвать из формы, подобной этой:

private void BackgroundWorkerButton_Click(object sender, EventArgs eventArgs)
{
    var operation = new SampleOperation();

    BackgroundWorkerButton.Enabled = false;

    BackgroundOperation(operation, (s, e) =>
        {
            BackgroundWorkerButton.Enabled = true;
        });
}

private void BackgroundOperation(ISteppedOperation operation, RunWorkerCompletedEventHandler runWorkerCompleted)
{
    var backgroundWorker = new BackgroundWorker();

    backgroundWorker.RunWorkerCompleted += runWorkerCompleted;
    backgroundWorker.WorkerSupportsCancellation = true;
    backgroundWorker.WorkerReportsProgress = true;

    backgroundWorker.DoWork += new DoWorkEventHandler((s, e) =>
    {
        while (operation.MoveNext())
        {
            operation.ProcessCurrent();

            int percentProgress = (100 * operation.CurrentStep) / operation.StepCount;
            backgroundWorker.ReportProgress(percentProgress);

            if (backgroundWorker.CancellationPending) break;
        }
    });

    backgroundWorker.ProgressChanged += new ProgressChangedEventHandler((s, e) =>
    {
        var progressChangedEventArgs = e as ProgressChangedEventArgs;
        this.progressBar1.Value = progressChangedEventArgs.ProgressPercentage;
    });

    backgroundWorker.RunWorkerAsync();
}

Я еще не сделал этого, но я перенесу BackgroundOperation () в собственный класс и реализую метод отменыоперация.

0 голосов
/ 02 августа 2011

Я бы поместил свой код без пользовательского интерфейса в новый класс и использовал бы поток (не фоновый рабочий). Чтобы показать прогресс, отправьте события нового класса обратно в пользовательский интерфейс и используйте Dispatcher.Invoke для обновления пользовательского интерфейса.

В этом есть немного кодирования, но оно чище и работает. И более удобен в обслуживании, чем использование фонового рабочего (который действительно предназначен только для небольших задач).

...