|
for safety reasons the lock object should be private, so nobody can mess with it. That is all there is to it.
|
|
|
|
|
|
all is fine if the outside world cannot touch the lock object, so if you have say a return _collection; somewhere, then the first is no good. Same for the second if you were to have a way to export _collectionLock somehow.
It is good practice to have a private member that is used for locking and locking only. That makes verification a bit easier.
|
|
|
|
|
|
Collin Jasnoch wrote: Why does it need an object just for locking?
it does not. I said "good practice".
|
|
|
|
|
|
Here[^] is 800+ pages of good practice. You can ignore it, and maybe get away with it; or you can learn to appreciate it, but that will take time.
|
|
|
|
|
|
Its a good practice because in most cases you have more than one object you have to sync over threads.
Now if you just use, lets say some list, you have to sync. Its possible to sync everything with the reference to the list object. After 5 months of work, your costumer says he needs some extras. Now how many work do you have if this extras not only need to sync this list but also some other variables (in most cases controlled by this list, but not in all) . Do you really want to lock the list every time you need to access some of this variables?
Thats why its an good practice, cause in the scenario above you do not have to change anything in your locking behavior, if you use an extra object.
Greetings
Covean
|
|
|
|
|
Thanks for sharing!
|
|
|
|
|
Because there are situations where you need one lock to cover operations on multiple objects.
There are also cases where you will be returning the object that you put the lock on. Since you just passed the object to an outside caller, the caller can now try to lock on that object as well, but it'll hang since there's already a lock there.
To avoid problems such as these, you would normally use a seperate lock object than the objects your are actually working with.
|
|
|
|
|
In addition to Luc's postings.
In your example it really doesn't matter if you use the collection itself or create an object for locking.
But how about this example (I just typed it here, not tested, hope you got what I mean):
private object m_objVideoPlayerSync = new object();
private List<string> m_lstVideos = new List<string>();
private EnumPlaybackState m_playbackState = EnumPlaybackState.Stopped;
private void AddVideoToList(string szVideoFileName)
{
lock(m_objVideoPlayerSync)
{
m_lstVideos.Add(szVideoFileName);
}
}
private void PlayNextVideo()
{
lock(m_objVideoPlayerSync)
{
string szVideo = m_lstVideos[0];
m_lstVideos.RemoveAt(0);
m_playbackState = EnumPlaybackState.Playback;
StartPlayBack();
}
}
private void StopVideo()
{
lock(m_objVideoPlayerSync)
{
StopPlayBack();
m_playbackState = EnumPlaybackState.Stopped;
}
}
In the function StopVideo() locking the list of videos would be crude, cause you never do anything on this list, but you need some object to lock.
But doing 2 locks in PlayNextVideo() like
string szVideo = string.Empty;
lock(m_lstVideos)
{
szVideo = m_lstVideos[0];
m_lstVideos.RemoveAt(0);
}
lock(m_objVideoPlayerSync)
{
m_playbackState = EnumPlaybackState.Playback;
StartPlayBack();
}
raises the chance that two threads, both starting the function PlayNextVideo() at the same, would play the same video.
In my first example this would never happen!
Here are some guidelines from MSDN how to use and not to use locks:
In general, avoid locking on a public type, or instances beyond your code's control. The common constructs lock (this), lock (typeof (MyType)), and lock ("myLock") violate this guideline:
lock (this) is a problem if the instance can be accessed publicly.
lock (typeof (MyType)) is a problem if MyType is publicly accessible.
lock(“myLock”) is a problem since any other code in the process using the same string, will share the same lock.
Best practice is to define a private object to lock on, or a private static object variable to protect data common to all instances.
I hope this helps you to understand locking a little bit better.
Greetings
Covean
|
|
|
|
|
Luc Pattyn wrote: All is fine if the outside world cannot touch the lock object, so if you have say a return _collection; somewhere, then the first is no good.
If you have a return _collection, you must expose whatever lock object you're using. You may as well use _collection itself as the lock object (and mandate that anyone who does anything with that collection do likewise). Returning _collection without exposing its lock object a guaranteed disaster. The idea that one should avoid exposing the lock for an exposed non-threadsafe object is absurd.
If a non-threadsafe object is accessed by two routines in different threads, the two routines must use the same lock object to arbitrate access. If the routines use different lock objects, nothing will prevent illegitimate concurrent access. I have no idea why this issue doesn't seem to be amplified more.
BTW, I also have no idea why Microsoft didn't come up with a more useful contract for iEnumerable--one that would allow for a collection to accept modifications during enumeration without throwing an exception, provided certain constraints were met.
|
|
|
|
|
you're right all the way.
Exporting a collection that uses a lock but does not make it available is absurd, and making it available is dangerous. Probably best is to provide the required functionality without exporting either the collection nor the lock. I'll have to remember not to start a sentence with "All is fine if".
For collections there should at least be an enumerator that allows modifications to the elements that have already been visited including removal from the collection, so one could e.g. easily remove elements that satisfy some conditions while enumerating them all. I understand it may be hard to come up with an implementation that is safe and inexpensive at the same time.
|
|
|
|
|
Luc Pattyn wrote: For collections there should at least be an enumerator that allows modifications to the elements that have already been visited including removal from the collection, so one could e.g. easily remove elements that satisfy some conditions while enumerating them all.
I would think there should be. Unfortunately, Microsoft's contract for iEnumerable/iEnumerator explicitly forbids them from even allowing such a thing; it requires that an attempt to fetch the next item from an iEnumerator throw an exception if any change has been made to the underlying iEnumerable even if the iEnumerator would otherwise be able to return something useful. Note that the vb6-style "Collection" object does not conform to this but instead returns useful data (hooray!) Too bad no other collections behave similarly.
If I had my druthers, an iEnumerator would be only be required to throw an exception if it couldn't otherwise meet the following contract:
- Any item that exists throughout the enumeration will be returned exactly once in each pass through the enumeration.
- Any item that is added and/or removed during the enumeration will be returned zero or one times (a removal and re-add may create a "new" item) in any pass.
- For collections with keys, changing a key may be regarded as a remove and add, which may be performed in either order.
- Items will be returned in a sequence consistent with the contract (e.g. if a particular collection is supposed to return items in sorted order, adding and deleting items should not cause items to be returned out of sequence).
- If the effect of an addition or removal is observed in one pass through the enumeration, it will be observed in subsequent passes.
For some types of collection, it would be difficult to meet this contract. Such collections should throw exceptions if they're modified during enumeration. On the other hand, if a collection can meet the above conditions even if it's changed during enumeration, why shouldn't it be allowed to?
|
|
|
|
|
I agree, that would be nice to have. I expect MS considers it too complex to sell; the existing stuff is very easy to explain, and they most often seem in favor of dumbing things down... ("You can always clone the collection!")
|
|
|
|
|
To whom would such a contract be 'too hard to sell'? If it would be too difficult to make a collection work nicely, it would be free to throw an exception. Provided that there's a means of determining whether a collection has been changed, I really don't see any advantages to requiring the exception. Further, if Microsoft was willing to disobey the contract on the vb6-style "Collection", why not offer any more useful form of collection that does likewise?
BTW, what would be the proper behavior for a collection which will require a .Dispose call to avoid a memory leak? To implement iEnumerable(of T) as well as iEnumerable, but throw NotImplementedException when a call is made to the non-generic GetEnumerator?
|
|
|
|
|
The way I see it, the programmer needs to be able to predict the behavior, so he can code accordingly. With a more complex behavior a class would risk to be less popular and/or lead to accidents, such as unexpected exceptions (because the user did not understand the contract well).
the vb6-style "Collection" looks like a compromise: what to do to ease VB->VB.NET upgrades while adhering to the .NET conventions? IMO they try to tell us a VB Collection is and is not a collection at the same time.
|
|
|
|
|
The way I see it, the programmer needs to be able to predict the behavior, so he can code accordingly. With a more complex behavior a class would risk to be less popular and/or lead to accidents, such as unexpected exceptions (because the user did not understand the contract well).
I guess I don't really see a whole lot of situations where the exception behavior is more useful than the behavior I described, especially since--if iEnumerator provided a collectionChanged property--one could code a generic class that, given an iEnumerator(of T), would implement iEnumerator(of T) but throw an exception if an effort was made to fetch an item after the underlying class had changed. Thus, if one wanted to have a "For Each" loop that would throw an exception if the collection changed, one could write:
For Each myThing as myClass in New ProtectedEnumerable(myCollection)
...
EndIf
Since one new generic ProtectedEnumerable() class could make any iEnumerable implement the present more-strict contract, I would think the less strict contract would be more useful.
If it were possible to simply implement and use an iNiceEnumerable which followed a different contract, I wouldn't mind the behavior of iEnumerable. As it is, however, iEnumerable and iDisposable are both hard-coded into the compilers for C# and VB.net; if a collection doesn't implement an interface which is called iEnumerable, it can't be used with for-each loop. Even if it would be useful to have an interface defined that implements Microsoft's strict contract, I don't think it's very nice to declare that only interfaces that adhere to that strict contract should be used in for-each loops.
Also, with regard to programmer confusion, it seems far less confusing to say that one must use certain collection types if one is going to modify a collection during enumeration, than to say that one must write enumeration code as:
Using MyNiceEnum as iNiceEnum(of MyThing) = myNiceCollection.GetNiceEnum
Dim MyItem as MyThing
While MyNiceEnum.GetNext
MyItem = MyNiceEnum.TheItem
.. Do something with MyItem
End While
End Using
as opposed to:
For Each MyItem in myNiceCollection
.. Do something with MyItem
Next
|
|
|
|
|
Now think about the implementation details of a protected collection. Most if not all collections are built on top of an array, which gets reallocated and copied every time the capacity becomes insufficient, and which gets insert/remove-shifted on each insertion or removal.
Such collection can have any number of users (methods, possibly active on different threads), each possibly holding one or more enumerators; each enumerator has its own state. Now insert or remove a collection item; how could the collection tell which protected enumerators get affected by the modification, and which don't (because they are already past the insertion/removal point). The natural way would be one of these:
1.
inside the collection, keep a collection of current enumerators and check each of them on every modification, not a pretty situation. (Note: there are life issues, probably needs WeakReferences)
2.
for each enumerator, create a copy of the array, basically cloning the collection. An expensive operation.
The current .NET behavior can easily be accomplished by having a "generation number" which is stored in the collection, incremented on every insert/remove, and gets copied into the enumerator when it gets created, then compared on every Next(). None of these are expensive, memory wise of cycle wise.
|
|
|
|
|
My contract could be just as easily honored. All any of the existing collection classes would have to do honor my 'improved' contract would be to behave exactly as they already do.
My complaint with the Microsoft's iEnumerable contract is that rather than merely requiring an enumerator to throw an exception if the collection has been modified in a way the enumerator can't sensibly handle, it requires that the enumerator throw an exception if the collection is modified, period. That seems unduly harsh.
If someone codes a collection using e.g. a linked list, such that insertions and deletions can be handled without disturbing enumeration (subject to the constraints I listed), is there any reason to forbid its enumerator from operating through insertions and deletions? I know that, from a practical standpoint, Microsoft isn't going to break the kneecaps of any programmer who has the audacity to do such a thing, but it still seems irksome that the best approach would be something Microsoft has declared to be wrong.
|
|
|
|
|
|
I don't think this is new or strange. You obviously can't use upper versions of framework components from lower versions.
I know the language. I've read a book. - _Madmatt
|
|
|
|
|
Actually I just had a similar experience:
a solution with one 3.5 EXE project referencing two 2.0 DLL projects; it was created long ago with 2008 without any problem (initially it was all 2.0, but then the EXE needed some LINQ and I never got around to modify the DLL targets). When ported to VS2010 I had to modify the DLL projects so they now all target the same framework.
|
|
|
|
|
Yes, but why VS2008 did not make this a problem on compiling?
I agree with you when you say you can't use upper framework form a 2.0 assembly, 'cause, for example it can be deployed on a win2000 or on a machine without .NET 3.0.
But as I say in my previous post, I'll deploy this solution on .NET 3.5 ready machines; only a bug/limit of Cobol<->.NET bridge force me to compile only this Cobol-.NET-interface-assembly in 2.0.
However, I found the trick: reference System.Core.dll (or other dll needed by your referenced assemblies) by hand.
Thanks!
Nando
Jesus
|
|
|
|
|