访问BackgroundWorker的DoWork事件处理程序中的类成员变量以及其他BackgroundWorker限制
问题 1
在BackgroundWorker 的DoWork 事件处理程序中,访问(读取和写入)包含BackgroundWorker 的类的成员变量是否安全?访问未在 DoWork 事件处理程序本身内部声明的其他变量是否安全?
显然,DoWork 不应该访问 WinForms 应用程序等任何 UI 对象,因为 UI 只能从 UI 线程更新。但是访问其他(与 UI 无关的)成员变量又如何呢?
我之所以问这个问题,是因为我在谷歌搜索时偶尔会看到评论说不允许访问成员变量。目前我能找到的唯一示例是 对此的评论MSDN 页面,其中写道:
请注意,如果 BGW 尝试访问或修改类级别变量,则可能会导致异常。所有数据都必须通过委托和事件传递给它。
还有:
从来没有。绝不。切勿尝试引用未在 DoWork 内部声明的变量。它有时似乎有效,但实际上你只是运气好。
据我所知,MSDN 本身没有记录任何此类限制(尽管如果我错了,我希望有一个链接)。但类似这样的评论似乎确实时不时地出现。
(当然,如果DoWork确实访问/修改了主线程可以同时访问/修改的成员变量,则有必要同步对该字段的访问,例如通过使用锁定对象。但是上面的引号似乎要求全面禁止访问成员变量,而不仅仅是同步访问!)
问题 2
为了使这个问题成为一个更普遍的问题,除了多于?也许有什么“最佳实践”?
Question 1
In the DoWork event handler of a BackgroundWorker, is it safe to access (for both reading and writing) member variables of the class that contains the BackgroundWorker? Is it safe to access other variables that are not declared inside the DoWork event handler itself?
Obviously DoWork should not be accessing any UI objects of, say, a WinForms application, as the UI should only be updated from the UI thread. But what about accessing other (not UI-related) member variables?
The reason why I ask is that I've seen the occasional comment come up while Googling saying that accessing member variables is not allowed. The only example I can find at the moment is a comment on this MSDN page, which says:
Note, that the BGW can cause exceptions if it attempts to access or modify class level variables. All data must be passed to it by delegates and events.
And also:
NEVER. NEVER. Never try to reference variables not declared inside of DoWork. It may seem to work at times, but in reality you are just getting lucky.
As far as I know, MSDN itself does not document any restrictions of this kind (although if I'm wrong, I'd appreciate a link). But comments like these do seem to pop up every now and again.
(Of course if DoWork does access/modify a member variable that could be accessed/modified by the main thread at the same time, it is necessary to synchronise access to that field, eg by using a locking object. But the above quotes seem to require a blanket ban of accessing member variables, rather than just synchronising access!)
Question 2
To make this into a more general question, are there any other (not documented?) restrictions that users of the BackgroundWorker should be aware of, aside from the above? Any "best practices", perhaps?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您在问题 1 中引用的评论错误。从 CLR 的角度来看,可以从 BackgroundWorker 访问表单类的成员(正如您所说,控件除外,因为它们具有线程关联性;但访问非 Control 成员(例如整数)也可以)。是的,正如您所注意到的,如果您这样做,那么您就需要正确同步访问:它始终处于多线程场景中。但糟糕的同步不会导致异常,正如第一个评论者所建议的那样:它只会导致良好的旧数据损坏(当然,这要好得多!)。说你“只是运气好”是错误的。这不是运气的问题,而是运气的问题。这是一个良好同步的问题。
为什么我用“从 CLR 的角度来看”来限定这个评论?首先,因为多线程访问状态很困难。因此,虽然这对于 CLR 来说不是问题,但对于对 CLR 进行编程的人来说可能是有问题的。其次,因为如果您的表单类包含BackgroundWorker 所需的大量非UI 内容,则可能表明该应用程序的结构很差。这可能意味着这些东西应该打包到一个对象中,并且BackgroundWorker应该调用该对象上的方法,而不是摆弄 Form 对象的状态。
The comments you quote in Question 1 are wrong. From a CLR point of view, it's fine to access members of the form class from your BackgroundWorker (except, as you say, for controls, because they have thread affinity; but accessing non-Control members such as integers is fine). Yes, as you note, if you do this, then it's up to you to synchronise access properly: it always is in a multithreading scenario. But bad synchronisation won't cause exceptions as the first commenter suggests: it will just cause good old data corruption (which is so much better of course!). And it's false to say that you're "just getting lucky." It's not a matter of luck; it's a matter of good synchronisation.
Why do I qualify this remark with "from a CLR point of view"? Firstly, because multithreaded access to state is difficult. So although it's not problematic for the CLR, it may be problematic for the human being programming the CLR. Secondly, because if your form class contains lots of non-UI stuff that's required by a BackgroundWorker, that may indicate that the application is poorly structured. It might mean that that stuff should be packaged up into an object, and the BackgroundWorker should call a method on that object rather than fiddling about with the state of the Form object.
如果变量是事件的成员,那就没问题。
如果变量是一个成员,比如说这个BackgroundWorker实例化的一个表单,我认为你应该在读取或写入值之前锁定它,即使对于整数,布尔值,字符串等原始类型也是如此。
请记住.NET 3.5 集合都不是线程安全的。
请留意此链接,因为它表达了您可能感兴趣的主题的一些其他观点和相关链接。
If the variable is a member of the event, it's okay.
If the variable is a member let's say of a form that this BackgroundWorker is intantiated, I think you should lock it before you read or write a value to it, even for primitive types such as integers, booleans, strings, etc.
Remember that the .NET 3.5 collections are neither threadsafe.
Please have an eye out to this link as it expresses some other point of views and related links on the subject that might interest you.
像大多数事情一样,这取决于情况。我想评论者写下这些警告是因为这些是常见的问题领域。遵循这个建议将大大帮助你摆脱麻烦,但它也带来了不必要的限制。如果您了解使用成员变量经常导致
BackgroundWorker
出现问题的原因,您就可以确定您的应用程序是否受到影响以及如何最好地解决这些问题。在 DoWork 中,对成员变量(也称为字段)的访问发生在工作线程中。第一个问题是,在后台工作程序启动后,某个线程会更新成员变量以引用 null 或另一个对象。如果可能的话,同步(C# 中的
synclock
关键字或其他机制)是合适的。第二个问题是,尽管引用可能是稳定的,但对象本身可能不是线程安全的。如果该类是 .NET Framework 的一部分,则该类的文档几乎总是解释它是否是线程安全的。如果它不是线程安全的,则在对引用该对象的成员变量的访问和对象本身的使用之间添加同步通常可以解决此问题。尤其要注意引用 Windows 窗体 GUI 对象(例如控件)的成员变量。虽然这些引用通常是稳定的,但通常这些对象不是线程安全的。更糟糕的是,同步锁无法解决该问题,因为 Windows 窗体对象有一个特殊要求,即对这些对象的调用必须在最初与这些对象关联的 GUI 线程上进行。要调用这些对象,您可以使用对象的
Invoke
或BeginInvoke
方法(在Control
类中定义)来强制代码在对象的 GUI 线程上运行。或者,对 GUI 的更新可以放置在BackgroundWorker
的ProgressChanged
和RunWorkerCompleted
事件中:与DoWork
不同,这些事件在 GUI 线程上引发。众所周知,多线程充满陷阱。
BackgroundWorker
有助于简化一些事情,但许多陷阱仍然存在。希望您看到对多线程编程的深入理解对于将BackgroundWorker
集成到您的应用程序中非常重要。Like most things, it depends. I suppose the commenter wrote those warnings because these are common problem areas. Following that advice will go a long way toward keeping you out of trouble, but it is also unnecessarily limiting. If you understand the reasoning why the use of member variables often leads to problems with
BackgroundWorker
, you can determine whether or not your application is affected and how best to resolve the problems.Within
DoWork
, the accesses to member variables (also known as fields) occur from the worker thread. The first concern is that the member variable is updated to refer to null or another object by some thread after the background worker has been started. If this is a possibility, synchronization (thesynclock
keyword in C# or another mechanism) is appropriate. The second concern is that although the reference might be stable, the object itself might not be thread safe. If the class is part of the .NET Framework, the documentation for the class almost always explains whether or not it is thread safe. If it is not thread safe, adding synchronization around both the access to the member variable that references the object and the use of the object itself will often resolve this.Especially be on the lookout for member variables that reference Windows Forms GUI objects (e.g., controls). While these references are usually stable, as a rule these objects are not thread safe. Worse yet,
synclock
will not resolve the problem because Windows Forms objects have a special requirement that calls to these objects must take place on the GUI thread originally associated with the objects. To make calls to these objects, you can use theInvoke
orBeginInvoke
methods of the object (defined in theControl
class) to force code to run on the GUI thread for the object. Alternatively, the updates to the GUI can be placed in theProgressChanged
andRunWorkerCompleted
events of theBackgroundWorker
: unlikeDoWork
, these events are raised on the GUI thread.Multithreading is notoriously full of pitfalls.
BackgroundWorker
helps simplify some things, but many of the pitfalls remain. Hopefully you see that a solid understanding of mulithreaded programming is important to integrateBackgroundWorker
in your applications.