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 U开发者_开发百科I-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?
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.
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.
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 (the synclock
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 the Invoke
or BeginInvoke
methods of the object (defined in the Control
class) to force code to run on the GUI thread for the object. Alternatively, the updates to the GUI can be placed in the ProgressChanged
and RunWorkerCompleted
events of the BackgroundWorker
: unlike DoWork
, 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 integrate BackgroundWorker
in your applications.
精彩评论