C#:我需要处置在运行时创建的BackgroundWorker吗?

发布于 2024-08-11 11:45:02 字数 479 浏览 9 评论 0原文

我通常在表单上有这样的代码:

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

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

        worker.RunWorkerAsync();
    }

这意味着我不会处置 BackgroundWorker,而如果我由表单设计者添加它,那么我认为它会被处置。

这会导致任何问题吗?声明一个模块级 _saveWorker,然后从表单的 dispose 方法中调用 Dispose 是否更正确?

I typically have code like this on a form:

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

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

        worker.RunWorkerAsync();
    }

This means that I don't dispose the BackgroundWorker, whereas if I had added it by the form designer then I think it would get disposed.

Will this cause any problems? Is it more correct to declare a module-level _saveWorker, and then call Dispose on it from the form's dispose method?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

诗笺 2024-08-18 11:45:02

是的,你应该处理掉后台工作者。

您可能会发现使用 ThreadPool.QueueUserWorkItem(...) 更容易,之后不需要任何清理。


关于为什么应该始终调用 Dispose() 的其他详细信息:

虽然如果您查看 BackgroundWorker 类,它实际上并没有在其 dispose 方法中执行任何线程清理,但调用 Dispose 仍然很重要,因为该类对垃圾收集器。

带有终结器的类不会立即被 GC 处理。它们被保留并添加到终结器队列中。然后,终结器线程运行(遵循标准模式调用 dispose)。这意味着该对象将在 GC 第 1 代中存活。第 1 代收集比第 0 代收集少得多,因此对象在内存中停留的时间更长。

然而,如果您调用 Dispose(),该对象将不会被添加到终结队列中,因此可以自由地被垃圾收集。

这并不是什么大问题,但如果您创建很多它们,您最终会使用比必要的更多的内存。始终对具有 dispose 方法的对象调用 dispose 确实应该被视为良好实践。

所以我想,总而言之,这不是一个 100% 硬性要求。如果您不调用 Dispose(),您的应用程序不会爆炸(甚至泄漏内存),但在某些情况下可能会产生负面影响。后台工作程序被设计为用作 WinForms 组件,因此以这种方式使用它,如果您有不同的要求并且不想将其用作 WinForms 组件,请不要使用它,请使用正确的工具来完成工作,就像线程池一样。

Yes, you should dispose of the background worker.

You may find it easier to use ThreadPool.QueueUserWorkItem(...) which doesn't require any clean up afterwards.


Additional detail on why you should always call Dispose():

Although if you look in the BackgroundWorker class it doesn't actually do any thread clean up in it's dispose method, it is still important to call Dispose because of the effect the class has on the garbage collector.

Classes with finalizers are not GCed immediately. They are kept and added to the finalizer queue. The finalizer thread then runs, (which following the standard pattern calls dispose). This means the object will survive into GC generation 1. And gen 1 collections are far rarer than gen 0 collections, so you object sticks around in memory for much longer.

If however you call Dispose(), the object will not be added to the finalization queue, so is free to be garbage collected.

It's not really big problem, but if you are creating a lot of them you'll end up using more memory than necessary. It should really be considered good practise to always call dispose on objects that have a dispose method.

So I suppose, all in all, it's not a 100% hard and fast requirement. Your app won't explode (or even leak memory) if you don't call Dispose(), but in some circumstances it may have negative effects. The background worker was designed for use from as a WinForms component so use it that way, if you have different requirements and don't want to use it as a WinForms component, don't use it, use the correct tool for the job, like the ThreadPool.

热血少△年 2024-08-18 11:45:02

挑战在于确保仅在 BackgroundWorker 运行完毕后对其进行处置。您不能在 Completed 事件中执行此操作,因为该事件是由 BackgroundWorker 本身引发的。

BackgroundWorker 实际上是用作 WinForms 表单上的组件,因此我建议要么这样做,要么切换到 Thread.QueueUserWorkItem 之类的东西。这将使用线程池线程,并且完成后不需要任何特殊的清理。

The challenge is making sure you only dispose the BackgroundWorker after it's finished running. You can't do that in the Completed event, because that event is raised by the BackgroundWorker itself.

BackgroundWorker is really intended to be used as a component on a WinForms form, so I would recommend either doing that, or switching to something like Thread.QueueUserWorkItem. This will use a thread-pool thread, and won't require any special cleanup when it's finished.

够运 2024-08-18 11:45:02

在我看来,一般来说,如果它是 IDisposable,那么当你完成它时,它应该是 Dispose()d。即使从技术上讲,BackgroundWorker 的当前实现不需要被处置,您也不希望对以后可能需要处置的内部实现感到惊讶。

In my view, in general, if it's IDisposable, it should be Dispose()d when you're done with it. Even if the current implementation of BackgroundWorker doesn't technically need to be disposed, you don't want to be surprised by later internal implementations that might.

等风来 2024-08-18 11:45:02

我不会打扰,Bgw 可以保留的唯一资源是线程,如果您的委托中没有无限循环,那么就可以了。

BackgroundWorker 从 Component 继承了 IDisposable() ,但实际上并不需要它。

将其与直接将方法推送到线程池进行比较。您不(不能)处置线程,当然不能处置池中的线程。

但是,如果您的示例是完整的,即您没有使用 Completed 事件或 Progress/Cancel 功能,那么您也可以使用 ThreadPool.QueueUserWorkItem() 。

I wouldn't bother, the only resource the Bgw could hold on to is the Thread and if you don't have an infinite loop in your delegate then you are fine.

BackgroundWorker inherits IDisposable() from Component but doesn't really need it.

Compare it to pushing your method directly on the ThreadPool. You don't (can't) Dispose threads, certainly not those from the Pool.

But if your sample is complete in the sense that you are not using the Completed event or Progress/Cancel features you could just as well use ThreadPool.QueueUserWorkItem().

所有深爱都是秘密 2024-08-18 11:45:02

为什么不包含在 using 语句中?不需要太多额外的努力,你就得到了处置:

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

编辑:

好吧,我做了一个小测试,看看处置发生了什么以及什么:

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 语句的范围内调用它,或者使用在代码中声明的它并立即处置它并取消引用它,它仍然正常运行。 Dispose 事件发生在主线程上,DoWork 和 RunWorkerCompleted 发生在线程池线程上(事件触发时可用的线程)。我尝试了一种情况,在调用 Dispose 之后(因此在 DoWork 有机会完成之前)立即注销了 RunWorkerCompleted 事件,并且 RunWorkerCompleted 没有触发。这让我相信,尽管 BackgroundWorker 对象已被释放,您仍然可以对其进行操作。

因此,正如其他人所提到的,此时似乎确实不需要调用 Dispose。然而,至少从我的经验和这些测试来看,我也不认为这样做有什么坏处。

Why not wrap in a using statement? Not much extra effort and you get the disposal:

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

EDIT:

Ok, I put together a little test to kinda see what is going on with disposal and whatnot:

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);
        }
    }
}

Here is the output:

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

From some testing, it appears that Dispose() has basically no effect on an initiated BackgroundWorker. Whether or not you call it in the scope of a using statement, or use it declared in code and immediately dispose it and dereference it, it still runs normally. The Disposed Event occurs on the main thread, and the DoWork and RunWorkerCompleted occur on thread pool threads (whichever one is available when the event fires). I tried a case where I unregistered the RunWorkerCompleted event right after I called Dispose (so before DoWork had a chance to complete) and RunWorkerCompleted did not fire. This leads me to believe that you can still manipulate the BackgroundWorker object despite it being disposed.

So as others have mentioned, at this time, it does appear as though calling Dispose is not really required. However, I do not see any harm in doing it either, at least from my experience and these tests.

可是我不能没有你 2024-08-18 11:45:02

RunWorkerCompleted 事件中调用 dispose。

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();

额外的 try/finally 是为了确保在完成代码引发异常时调用 Dispose

Call dispose in your RunWorkerCompleted event.

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();

The extra try/finally is to ensure that Dispose gets called if your completion code raises an exception.

如歌彻婉言 2024-08-18 11:45:02

为所有 IDisposable 对象调用 Dispose() 被认为是最佳实践。这允许他们释放他们可能持有的非托管资源,例如句柄。 IDisposable 类还应该具有终结器,它的存在会延迟 GC 完全收集这些对象的时间。

如果您的类分配了 IDisposable 并将其分配给成员变量,那么通常它本身也应该是 IDisposable。

但是,如果不调用Dispose(),那么Finalizer最终会被调用,资源最终会被清理掉。显式调用它的优点是这些事情可以更快地发生并且开销更少,从而提高应用程序性能并减少内存压力。

It's considered a best practice to call Dispose() for all IDisposable objects. That allows them to release unmanaged resources they may be holding, such as Handles. IDisposable classes also should have finalizers, whose presence can delay the time at which the GC is allowed to fully collect those objects.

If your class allocates an IDisposable and assigns it to a member variable, then in general it should itself also be IDisposable.

However, if you don't call Dispose(), then the Finalizer will eventually be called, and the resources will eventually be cleaned up. The advantage of calling it explicitly is that those things can happen more quickly and with less overhead, which improve app performance and reduce memory pressure.

生生不灭 2024-08-18 11:45:02

完成处理程序在原始线程上运行(即,不是线程池中的后台线程)!你的测试结果实际上证实了这个前提。

The completion handler is run on the original thread (i.e., not the background thread from the thread pool)! Your test results actually confirm that premise.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文