Scope and more

Source: Internet
Author: User

I have been using the new transactionscope class in. NET Framework 2.0. I like the model it provides. To start a transaction, I can use transaction in a method to create a transactionscope. Then, in the other method called by this method, I can create a sqlcommand automatically listed in this transaction. But how does sqlcommand know the previously created transaction? More importantly, how can I simulate this function in my own classes?

A: If you are not familiar with transactionscope, transactionscope is part of the system. Transactions namespace added in Microsoft. NET Framework 2.0. System. Transactions provides a transaction framework (including but not limited to ADO. Net) that is fully integrated into the. NET Framework ). Transaction and transactionscope are the two most important classes in this namespace. As mentioned in the question, you can create a transactionscope instance and then automatically list the ADO executed in the scope of the transactionscope. net Operation (you can also use transaction. current static attribute to access the current transaction ):

 
Using (transactionscope scope = new transactionscope () {... // all operations here are part of the scope. Complete ();}

Transactionscope has multiple constructors, some of which accept the transactionscospontion enumeration value, telling transactionscope whether to create a new transaction, whether to use any possibly existing environmental transaction, or whether to cancel any environmental transaction. If I execute the followingCode(This Code creates a nested transactionscope, which requires a new transaction instead of an existing environment transaction). I will see that the local identifier of the original transaction is output first, followed by the identifier of the new transaction, and then the original identifier:

 
Using (New transactionscope () {console. writeline (transaction. current. transactioninformation. localidentifier); Using (New transactionscope (transactionscospontion. requiresnew) {console. writeline (transaction. current. transactioninformation. localidentifier);} console. writeline (transaction. current. transactioninformation. localidentifier );}

Any number of transactionscope can be nested. In the internal, the system. Transaction namespace retains the stacks of all environmental transactions.

At its core, static thread members provide the following models. If a type contains a non-static field (instance field), each instance of this type has its own independent storage location; setting a field in one instance does not affect the value of this field in other instances. On the contrary, for a static field, no matter how many instances there are, this field is only in one storage location (or, more specifically, in each appdomain, only in one storage location ). However, if. when threadstaticattribute is applied to a static field, the field becomes a static thread field. For this field, each thread (rather than the instance) retains its own storage location. Setting static values of a thread does not affect the values of other threads. People familiar with Visual C ++ will know that, in terms of concept, this function is similar to this function in _ declspec (thread) and is used to declare local variables in the thread.

Static thread fields can be used in various situations. A common usage is to store non-thread-safe Singleton instances. By creating a singleton for each thread instead of the entire appdomain, the thread security can be ensured without explicit locking, because only one thread can access the variable, only one thread can access the instance (of course, this security will be lost if the thread transfers the reference to the singleton object to another thread ). In fact, when a static Singleton is used, two types of locks are usually required, one is used for initialization (if Initialization is completed in the type of static constructor, it is an implicit lock, if delayed Initialization is used, the display is locked), and the other is used to access the actual instance. The two locks are not required for the static thread Singleton.

Another usage of static thread fields is (which brings us back to transactionscope) used to transmit out-of-band data between method calls. In a non-object-oriented language, generally, the data that a function has the right to access can only be the data that is provided to it through the display of parameters or global variables. However, in an object-oriented system, there are many other methods, such as instance fields (if the method is an instance method) or static fields (if the method is a static method ).

Assume that transactionscope can use static fields to store the current transaction, while sqlcommand only obtains the corresponding transaction from the static field. However, in the case of multithreading, this may cause major problems because the two transactionscopes may attack each other. A thread may overwrite the current transaction reference recently released by another thread. As a result, multiple threads may eventually use the same transaction instance, which may cause a disaster.

This time, static thread fields are useful. Since the data in the stored online static field is only visible to the code running in the same thread where the data is stored, you can use this field to pass other data from a method to other methods called by the first method, and there is no need to worry that other threads will destroy its work. Now, assume that transactionscope uses similar technologies. After instantiation, it stores the current transaction in the static thread field. When you instantiate sqlcommand later (before this transactionscope is deleted from the local storage of the thread), The sqlcommand checks static thread fields to find existing transactions. If yes, the sqlcommand is included in the transaction. In this way, transactionscope and sqlcommand can work together, so that developers do not have to pass transaction display to sqlcommand objects. In fact, the mechanisms used by transactionscope and sqlcommand are very complex, but the core is that the premise should be reasonable.

You can use static thread fields to create similar systems of any type. Figure 1 shows a class I wrote: Scope <t>, which implements some similar behavior. The idea is: You can use an instance of type T in a method to instantiate the scope <t> and want to call another method in the stack to access the instance later. You can use scope <t>. Current to access the instance. Scope <t> also supports nesting, that is, inside, it retains the instance stack of type T and exposes the instance to the top of the stack through the current attribute. In this way, if a method creates a scope with a T-type instance and then calls another scope <t> Method for instantiating another T-type instance, the instance in the first scope <t> will not be lost. You can use the scope <t> after deleting the nested scope. current again.

The scope of environment attributes (such as scope <t>. Current) should be almost always restricted. In other words, you need to publish and revoke these attributes in the same stack frame (usually through try/finally blocks ). This is also true for transactionscope, locking, and simulation. This is also why scope <t> implements idisposable: Scope <t> will be used in the using block, so that the constructor release instance and the dispose method can cancel the instance.

The following example can help you consolidate the content described above. Figure 2 shows an example of how to use a scope with a streamwriter instance <t>. First, the main method writes "mainenter" to the streamwriter returned by scope <streamwriter>. Current. Because there is no active scope <streamwriter> or scope <streamwriter>. current will return NULL (this is why I used a helper write method, which is used to check whether it is null before writing to streamwriter, otherwise nullreferenceexception will be generated ). Then, for the file c: \ test1.txt, use streamwriter to create a new scope <streamwriter> and call the firstmethod method. Firstmethod writes "firstmethodenter" to the current streamwriter. At the same time, the text is output to c: \ test1.txt because it is the streamwriter in the current scope. Firstmethod continues to set a new scope <streamwriter>, this time for c: \ test2.txt. Internally, for scope <t>, this will cause the new streamwriter to be pushed into the internal stack, located above the existing streamwriter of c: \ test1.txt. In this case, scope <streamwriter>. current will return a reference to streamwriter in c: \ test2.txt. Therefore, the call to secondmethod will cause "secondmethod" to be written to c: \ test2.txt rather than c: \ test1.txt. Firstmethod: continue to delete scope <streamwriter> of c: \ test2.txt (by using the Using Keyword), pop up the streamwriter from the internal stack, and restore the streamwriter of c: \ test1.txt to the current streamwriter, in this way, "firstmethodexit" is written to the scope <streamwriter>. current will point the text to c: \ test1.txt. Finally, return to main, the original scope <streamwriter> is deleted, and the internal stack of streamwriter is cleared. In this way, scope <streamwriter>. Current returns NULL again. Okay, so rest assured.

Internally, scope <t> relies on static thread field storage to provide the stack of the instance of the scope <t> constructor <t>. Each time a scope <t> is constructed, the stack of the thread is retrieved from the instances attribute <t>, and the new T is pushed into it. The static current attribute uses the stack <t>. Peek method to return the T instance at the top of the stack (if any). If the stack <t> is null, null is returned. When scope <t> is deleted, the top item is displayed from the stack.

If you look back at figure 1, you may want to know why I used delayed initialization for the stack <t> Field _ instances. You know, you must create this instance, And the scope <t> constructor can run successfully, so the delay Initialization is too high, isn't it? Of course not. I can delete the delayed initialization, And then I only need to change the declaration of this field to include the initialization:

 
Private Static stack <t> instances {return _ instances;} [threadstatic] Private Static stack <t> _ instances = new stack <t> ();

However, if you make the above changes and use scope <t> in multiple threads, when I try to construct a new instance of scope <t>, I may start to see the thrown nullreferenceexception. Although this field has been marked with the threadstatic attribute, the initialization of this field is actually part of the Type Static constructor, therefore, it is executed only once and only on the thread that runs the type of static Constructor (the first thread that wins access to its status. All scopes used on this thread can work normally, because the _ instances field has been correctly initialized, but on all other threads, _ instances will remain empty, this causes nullreferenceexception every time the scope <t> constructor tries to push an item into a non-existent stack.

The following are several other implementation details that you should pay attention. First, after the instance is popped up from the stack, the dispose method checks whether there are any remaining instances in the stack. If it does not exist, it will set the thread static stack <t> field to null. Static thread fields have a good attribute, that is, once the managed thread disappears, they will no longer be treated as GC root. However, if the thread using scope <t> occasionally has a long lifetime, the contained stack <t> may be retained for much longer than the required time. If a large number of threads use scope <t> over time, it may result in expansion. Therefore, we recommend that you press instances into the stack only when the stack contains instances. When the stack is empty, the stack will be released.

Note that the static beginthreadaffinity and endthreadaffinity methods of the thread class are called. These two methods exist because. NET Framework 2.0 initially allows the CLR host to provide its own thread management, allows the host to run the managed thread as a fiber, and provides its own fiber scheduling. In this way, the CLR host can move ongoing tasks from one physical OS thread to another at any time. However, depending on the specific content processed by the managed code, the tasks being executed may have a required thread correlation level, which means they may need to be located on the same physical OS thread During task execution. Therefore, the beginthreadaffinity and endthreadaffinity methods are introduced to allow the hosted code to notify the host that tasks cannot be moved when the request thread is correlated. Since scope <t> uses local thread storage (which is associated with physical threads rather than fiber threads), it is very important to use these two methods. Otherwise, when a thread is executed, scope <t> can store the data to the static state of the thread, but if it is not known to scope <t>, the task may be moved to another OS thread with completely different static thread values. In fact, we do not approve of the support for the fiber process. Therefore, failing to call these two methods correctly does not cause any harm. That is to say, in the future version of the framework, it is likely to introduce support for the fiber process again. Therefore, you should develop the habit of using these two methods to avoid headaches during debugging in the future.

Finally, note that scope not only pushes the provided instance to the stack, but also stores it in the instance field. This is used to verify whether the scope <t> is correct and whether the cleaning order is correct. After the instance is popped up from the stack, the instance will be compared with the cached instance in scope <t>; the two instances should always be the same. If they are different, the order of deleting nested scope <t> instances is incorrect.

Of course, since we have stored the instance provided by the user in scope <t>, do we still need stack <t>? In fact, we can cancel it. We only need to link the nested scope <t> instance to a link list and store the list header in the online process static state, each scope <t> stores references to the previous scope <t>. This not only simplifies the design, but also removes unnecessary allocation related to maintenance stacks. Figure 3 shows the updated scope <t> version.

I like the new semaphore class in. NET Framework, but it does not have the monitor class (where C # and Visual Basic provide the lock and synclock keywords. Why does semaphore not provide similar functions?

A: With a short piece of code, it will become equally simple. Figure 4 contains a lightweight packaging class that can be used to simulate lock/synclock behavior, but replaces monitor with semaphore. Disposable. Lock is a static method that accepts semaphore. Wait for semaphore, and then return a new instance of the class used to implement idisposable. This instance calls dispose to release semaphore. The following code allows you to use the using keyword for semaphore to wait for it, perform some work, and then release it:

 
Using (disposable. Lock (thesemaphore) {... // work here}

The implementation in Figure 4 is simple, but it uses two methods that you may not be familiar with: thread. begincriticalregion and thread. endcriticalregion. The critical section indicates that the impact of asynchronous or unprocessed exceptions in this area may not only be attributed to the current task, but may also cause instability of the entire appdomain. For example, when processing cross-thread data, a thread may be interrupted and may not be able to reset everything to a valid state. If a fault occurs in the critical section (such as threadabortexception), the CLR host (such as SQL Server 2005) can choose to stop the entire appdomain, instead of taking the risk of continuing execution in a potentially unstable state. When a thread enters or exits the critical section, the CLR must be notified so that when a fault occurs, the CLR can notify the host accordingly. This is why thread. begincriticalregion and thread. endcriticalregion exist. Since the lock is used for inter-thread communication, canceling the lock will enable the critical section, and releasing the lock will end the critical section. This works. This logic is built into monitor, so I have included it in the disposable. Lock implementation in Figure 4. For more information, see one of my articles in the October 2005 msdn magazine.ArticleThe URL is msdn.microsoft.com/msdnmag/issues/05/10/reliability.

Http://www.microsoft.com/china/MSDN/library/netFramework/netframework/NETMattersSep.mspx

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.