C #: мне нужно утилизировать BackgroundWorker, созданный во время выполнения? - PullRequest
31 голосов
/ 18 ноября 2009

У меня обычно есть такой код на форме:

    private void PerformLongRunningOperation()
    {
        BackgroundWorker worker = new BackgroundWorker();

        worker.DoWork += delegate
        {
            // perform long running operation here
        };

        worker.RunWorkerAsync();
    }

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

Это вызовет проблемы? Правильнее ли объявить уровень модуля _saveWorker, а затем вызвать Dispose для него из метода dispose формы?

Ответы [ 8 ]

32 голосов
/ 18 ноября 2009

Да, вы должны избавиться от фонового работника.

Возможно, вам будет проще использовать ThreadPool.QueueUserWorkItem(...), который впоследствии не требует очистки.


Дополнительная информация о том, почему вы всегда должны вызывать Dispose ():

Хотя если вы посмотрите на класс BackgroundWorker, он на самом деле не выполняет очистку потока в своем методе dispose, но все равно важно вызвать Dispose из-за влияния класса на сборщик мусора.

Классы с финализаторами не GCed сразу. Они сохраняются и добавляются в очередь финализатора. Затем запускается поток финализатора (который после стандартных вызовов шаблона удаляет). Это означает, что объект выживет в GC поколения 1. И коллекции 1-го поколения гораздо реже коллекций поколения 0, поэтому объект остается в памяти гораздо дольше.

Если, однако, вы вызываете Dispose (), объект не будет добавлен в очередь завершения, поэтому его можно будет собирать мусором.

Это не очень большая проблема, но если вы создаете много из них, вы в конечном итоге будете использовать больше памяти, чем необходимо. На самом деле следует считать хорошей практикой всегда вызывать dispose для объектов, у которых есть метод dispose.

Так что, в общем, это не 100% жесткое и быстрое требование. Ваше приложение не взорвется (или даже не вытечет из памяти), если вы не вызовете Dispose (), но в некоторых случаях это может иметь негативные последствия. Фоновый рабочий был разработан для использования в качестве компонента WinForms, поэтому используйте его таким образом, если у вас есть другие требования и вы не хотите использовать его в качестве компонента WinForms, не используйте его, используйте правильный инструмент для работы, как ThreadPool.

16 голосов
/ 18 ноября 2009

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

BackgroundWorker действительно предназначен для использования в качестве компонента в форме WinForms, поэтому я бы порекомендовал либо сделать это, либо переключиться на что-то вроде Thread.QueueUserWorkItem. При этом будет использован поток пула потоков, и по его окончании не потребуется никакой специальной очистки.

7 голосов
/ 18 ноября 2009

На мой взгляд, в общем случае, если это IDisposable, это должно быть Dispose () d, когда вы закончите с ним. Даже если текущая реализация BackgroundWorker технически не нуждается в удалении, вы не хотите удивляться более поздним внутренним реализациям, которые могут.

6 голосов
/ 18 ноября 2009

Я бы не стал беспокоиться, единственный ресурс, на котором может держаться Bgw, - это Thread, и если у вас нет бесконечного цикла в вашем делегате, то все в порядке.

BackgroundWorker наследует IDisposable() от Компонента, но в действительности это не нужно.

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

Но если ваш образец завершен в том смысле, что вы не используете событие Completed или функции Progress / Cancel, вы также можете использовать ThreadPool.QueueUserWorkItem().

3 голосов
/ 17 июня 2011

Вызовите распоряжение в вашем RunWorkerCompleted событии.

BackgroundWorker wkr = new BackgroundWorker();
wkr.DoWork += (s, e) => {
    // Do long running task.
};
wkr.RunWorkerCompleted += (s, e) => {
    try {
        if (e.Error != null) {
            // Handle failure.
        }
    } finally {
        // Use wkr outer instead of casting.
        wkr.Dispose();
    }
};
wkr.RunWorkerAsync();

Дополнительная попытка / finally заключается в обеспечении вызова Dispose, если ваш код завершения вызывает исключение.

3 голосов
/ 25 ноября 2009

Почему бы не заключить в оператор использования? Не так много дополнительных усилий, и вы получите распоряжение:

private void PerformLongRunningOperation()
    {
        using (BackgroundWorker worker = new BackgroundWorker())
        {
            worker.DoWork += delegate
                             {
                                 // perform long running operation here 
                             };
            worker.RunWorkerAsync();
        }
    }

EDIT:

Хорошо, я собрал небольшой тест, чтобы увидеть, что происходит с утилизацией и еще много чего:

using System;
using System.ComponentModel;
using System.Threading;

namespace BackgroundWorkerTest
{
    internal class Program
    {
        private static BackgroundWorker _privateWorker;

        private static void Main()
        {
            PrintThread("Main");
            _privateWorker = new BackgroundWorker();
            _privateWorker.DoWork += WorkerDoWork;
            _privateWorker.RunWorkerCompleted += WorkerRunWorkerCompleted;
            _privateWorker.Disposed += WorkerDisposed;
            _privateWorker.RunWorkerAsync();
            _privateWorker.Dispose();
            _privateWorker = null;

            using (var BW = new BackgroundWorker())
            {
                BW.DoWork += delegate
                                 {
                                     Thread.Sleep(2000);
                                     PrintThread("Using Worker Working");
                                 };
                BW.Disposed += delegate { PrintThread("Using Worker Disposed"); };
                BW.RunWorkerCompleted += delegate { PrintThread("Using Worker Completed"); };
                BW.RunWorkerAsync();
            }

            Console.ReadLine();
        }

        private static void WorkerDisposed(object sender, EventArgs e)
        {
            PrintThread("Private Worker Disposed");
        }

        private static void WorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            PrintThread("Private Worker Completed");
        }

        private static void WorkerDoWork(object sender, DoWorkEventArgs e)
        {
            Thread.Sleep(2000);
            PrintThread("Private Worker Working");
        }

        private static void PrintThread(string caller)
        {
            Console.WriteLine("{0} Thread: {1}", caller, Thread.CurrentThread.ManagedThreadId);
        }
    }
}

Вот вывод:

Main Thread: 1
Private Worker Disposed Thread: 1
Using Worker Disposed Thread: 1
Private Worker Working Thread: 3
Using Worker Working Thread: 4
Using Worker Completed Thread: 4
Private Worker Completed Thread: 3

Из некоторых тестов выясняется, что Dispose () практически не влияет на инициированный BackgroundWorker. Независимо от того, вызываете ли вы его в области действия оператора using или используете его, объявленный в коде, и сразу же удаляете его и разыменовываете, оно все равно работает нормально. Событие Disposed происходит в главном потоке, а DoWork и RunWorkerCompleted происходят в потоках пула потоков (в зависимости от того, какой из них доступен при возникновении события). Я пробовал случай, когда я незарегистрировал событие RunWorkerCompleted сразу после того, как вызвал Dispose (так, прежде чем у DoWork была возможность завершиться), а RunWorkerCompleted не сработал. Это наводит меня на мысль, что вы все еще можете манипулировать объектом BackgroundWorker, несмотря на его удаление.

Итак, как уже упоминали другие, в настоящее время кажется, что вызов Dispose на самом деле не требуется. Тем не менее, я не вижу никакого вреда в этом, по крайней мере, из моего опыта и этих тестов.

2 голосов
/ 18 ноября 2009

Рекомендуется вызывать Dispose () для всех объектов IDisposable. Это позволяет им высвобождать неуправляемые ресурсы, которые они могут держать, такие как дескрипторы. Классы IDisposable также должны иметь финализаторы, присутствие которых может задержать время, в которое GC разрешается полностью собирать эти объекты.

Если ваш класс выделяет IDisposable и назначает его переменной-члену, то в общем случае он также должен быть IDisposable.

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

0 голосов
/ 20 февраля 2013

Обработчик завершения выполняется в исходном потоке (т. Е. Не в фоновом потоке из пула потоков)! Результаты вашего теста фактически подтверждают это предположение.

...