This section explains the trade-offs of using stateful objects in your applications.
In the previous section, you saw that the time per transaction in MoveMoney and Stateful MoveMoney using a single Sample Bank client is nearly the same. However, as the number of concurrent transactions increases, MoveMoney begins to outperform Stateful MoveMoney significantly . At first glance, the code doesn't seem to account for the lag.
Class member variables for each account must be set before calling StatefulPerform, whereas Perform passes the account numbers by value through function parameters. The call to return the value of the account number in the MoveMoney object isn't an intensive operation. So what explains the performance degradation?
The reason is that Microsoft Transaction Server cannot commit transactions until it completes a method call. To maintain internal state, additional method calls are made on the MoveMoney object, thereby delaying the object from completing its work. This delay may cause server resources, such as database connections, to be held longer, therefore decreasing the amount of resources available for other clients. In other words, the application won't scale well.
The following diagram illustrates this point. The arrow on the left indicates time, which translates into performance. The arrow on the right indicates the server resources consumed, which translates into throughput. Transaction A represents a call made to stateless objects. On return from the method call, Transaction Server determines that the transaction can be committed, allowing the object to release its resources and be deactivated. On the other hand, Transaction B holds state between method calls, which increases the time that the server holds onto resources for that transaction. As the number of clients increases, so does the time required for transactions to be completed.
Examine the following excerpt from the Sample Bank client code (some code has been omitted for clarity).
For i = 1 To nTrans
.
.
.
obj.PrimeAccountNo = PrimeAcct
obj.SecondAccountNo = lSecondAcct
lRet = obj.StatefulPerform(CLng(Amount), TranType, Res)
.
.
.
Next i
Because the account numbers don't change, you might be inclined to rearrange the code as follows:
obj.PrimeAccountNo = PrimeAcct
obj.SecondAccountNo = lSecondAcct
For i = 1 To nTrans
lRet = obj.StatefulPerform(CLng(Amount), TranType, Res)
Next i
If you modify the code and then run the Sample Bank client for multiple transactions, the application fails on the second transaction. Why?
The answer is subtle. MoveMoney uses SetComplete to notify Transaction Server that it has completed its work. At this point, the MoveMoney object is deactivated. In the process of deactivation, all of the object's member variables are reinitialized. The next call to MoveMoney causes just-in-time activation. The activated object is now in its initial state, meaning the values of PrimeAccountNo and SecondAccountNo are both zero. Thus, the next call to StatefulPerform fails because of an invalid account number.
This is yet another reason to be careful when maintaining state in objects. Clients of application objects must be aware of how an object uses SetComplete to ensure that any state the object maintains won't be needed after the object undergoes just-in-time activation.
Transactions, Deactivating Objects, Context Objects, Stateful Components, ObjectContext object, SetComplete method