In the example used so far, no special code is used to configure the lifetime of the remote object. For a SingleCall object, no such code is required because the object is automatically destroyed at the end of each method call. For client-activated or singleton objects, however, the remote object is automatically destroyed if it is inactive for 2 minutes, provided it has been in existence for at least 5 minutes. These magic numbers are just the default properties of the lifetime lease automatically assigned to every remote object. They are encapsulated by the ILease interface in the System.Runtime.Remoting.Lifetime namespace. Table 4-2 lists the properties of the ILease interface.
All remotable objects gain a special MarshalByRefObject.GetLifeTimeService method that returns the ILease instance currently assigned to the remote object. You can make use of this property to retrieve and examine the lease. The next example modifies the Windows client to display information about the lease in a label control. (See Listing 4-24.) The client uses a timer and displays the current lease time in response to each timer tick. When you run the form, you'll see the lease time count down, as shown in Figure 4-11. Listing 4-24 Tracking a leaseImports System.Runtime.Remoting Imports System.Runtime.Remoting.Lifetime Public Class ClientForm Inherits System.Windows.Forms.Form ' (Designer code omitted.) Private RemoteObj As RemoteObjects.RemoteObject Private Sub ClientForm_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load ' Create the remote object. RemotingConfiguration.Configure("SimpleClient.exe.config") RemoteObj = New RemoteObjects.RemoteObject() ' Start the timer. tmrCheckLease.Interval = 1000 tmrCheckLease.Start() End Sub ' When the timer fires, retrieve all the lease information and ' update the display. Private Sub tmrCheckLease_Tick(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles tmrCheckLease.Tick Try Dim Lease As ILease = RemoteObj.GetLifetimeService() If Not (Lease Is Nothing) Then ' Display current lease information. lblLease.Text = "Current State: " lblLease.Text &= Lease.CurrentState.ToString() lblLease.Text &= Environment.NewLine lblLease.Text &= "Initial Time Allocation: " lblLease.Text &= Lease.InitialLeaseTime.ToString() lblLease.Text &= Environment.NewLine lblLease.Text &= "Time Remaining: " lblLease.Text &= Lease.CurrentLeaseTime.ToString() End If Catch err As RemotingException lblLease.Text = err.ToString() tmrCheckLease.Stop() End Try End Sub End Class Figure 4-11. Tracking a remote object lease
When the lease dips below 2 minutes, something interesting happens. Because just retrieving the lease counts as interaction with the remote object, it automatically renews the lease lifetime to 2 minutes (according to the default RenewOnCallTime). This process happens perpetually, ensuring that the object is never destroyed. You can circumvent this behavior by retrieving and storing a reference to the ILease object when your program starts. First, create the ILease reference as a form-level variable: Private Lease As ILease Then retrieve the lease in the Form.Load event: Private Sub ClientForm_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load ' (Other initialization code omitted.) Lease = RemoteObj.GetLifetimeService() End Sub Now, even though the lease is retrieved from the remote application domain, by using a direct reference you bypass the remote object to which it applies. When you run the program, you will see the lease count down until it reaches zero, the object is deallocated, and a RemotingException is thrown. Modifying LifetimeYou can modify an object's lifetime in several ways. From the client's point of view, you can just call a method or set a property on the remote object, which automatically returns the lifetime to the RenewOnCallTime if it has dipped below this number. However, the client can't directly modify any of the ILease properties because they are all read-only. Instead, you can use the ILease.Renew method, which accepts a TimeSpan object and updates the CurrentLeaseTime accordingly. To test the renewal method, you can add a Renew method to the lease test client, with the event handler shown in Listing 4-25. Listing 4-25 Renewing a leasePrivate Sub cmdRenew_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdRenew.Click ' Use the TimeSpan constructor for (Hours, Minutes, Seconds). Lease.Renew(New TimeSpan(0, 1, 0)) End Sub If you click this button while the current lease time is greater than 1 minute, nothing happens. If you click this button after the lease time has dipped below 1 minute, it automatically resets the current lease time to 1 minute. The component host can also play a hand in controlling the lifetime of the remote object. Generally, this is accomplished by setting lease defaults in the server configuration file, as shown in Listing 4-26. (These settings are case-sensitive and mirror the ILease properties, but with an initial lowercase letter.) Listing 4-26 Configuring lifetime in the host configuration file<configuration> <system.runtime.remoting> <application name="SimpleServer"> <service> <activated type="RemoteObjects.RemoteObject, RemoteObjects"/> </service> <channels> <channel ref="tcp server" port="8080" /> </channels> <lifetime leaseTime = "10M" renewOnCallTime = "1M" sponsorshipTimeout = "0" leaseManagerPollTime = "30S" /> </application> </system.runtime.remoting> </configuration> The lease settings apply to all the remote objects created by this component host in this application domain. Use a trailing M for minutes or an S to indicate seconds. In the preceding example, a remote object starts with an initial lifetime of 10 minutes and is renewed to 1 minute as required when the client interacts with it. By setting sponsorshipTimeOut to 0, we specify that the lease won't use any sponsors. You can test these settings by running the lease client test. You also can try setting the leaseTime attribute to 0 minutes, which configures the remote object to have an infinite lifetime. In this case, the GetLifetimeService method returns a null reference (Nothing). Alternatively, the remotable object can override the InitializeLiftetimeService method and take control of its own destiny. In this method, the remote object can modify the ILease properties, provided the lease hasn't yet been initialized. (See Listing 4-27.) Listing 4-27 Overriding lease settingsPublic Overrides Function InitializeLifetimeService() As Object Dim Lease As ILease = MyBase.InitializeLifetimeService() ' Lease can only be configured if it is in an initial state. If Lease.CurrentState = LeaseState.Initial Then Lease.InitialLeaseTime = TimeSpan.FromMinutes(10) Lease.RenewOnCallTime = TimeSpan.FromMinutes(1) End If Return Lease End Function If you want to set the object to have an infinite lifetime, you can bypass the leasing mechanism by returning a null reference, as shown in Listing 4-28. Listing 4-28 An object with an infinite lifetimePublic Overrides Function InitializeLifetimeService() As Object Return Nothing End Function This is a common approach for a singleton object, which you often want to exist even when a client isn't using it. Using a Leasing SponsorThe last option is to use a leasing sponsor, which is a dedicated class that handles object renewal. When a lease manager discovers an expired lease, it first attempts to contact any registered sponsors. It then gives them the opportunity to renew the lease. This is generally better than manually renewing the lease in the client because you don't have to continuously check the current lease time. Instead, the sponsor is contacted only when necessary. To create a sponsor, all you need is a class that implements System.Runtime.Remoting.Lifetime.ISponsor and derives from MarshalByRefObject (so that it can be called across a network). ISponsor defines a single method, called Renewal, which is called by the .NET runtime to extend the lifetime of registered objects. The class library also provides a ClientSponsor class that provides a default ISponsor implementation, complete with methods for registering and unregistering the sponsor and a RenewalTime property that sets the lease time that will be given to the remote object on renewal. Listing 4-29 shows an example in which the lease sponsor is created in the same domain as the client application, ensuring that if the client is disconnected, the sponsor will also be unreachable (and the remote object will be destroyed). Figure 4-12 shows the arrangement. Listing 4-29 A custom lease sponsorPrivate Sponsor As New ClientSponsor() Private Sub HostForm_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load ' Create the remote object. RemotingConfiguration.Configure("SimpleClient.exe.config") RemoteObj = New RemoteObjects.RemoteObject() ' Configure and register the sponsor. Sponsor.RenewalTime = TimeSpan.FromMinutes(5) Sponsor.Register(RemoteObj) ' Start the timer. tmrCheckLease.Interval = 1000 tmrCheckLease.Start() End Sub Figure 4-12. Using a lease sponsor
Figure 4-12 illuminates one other detail: The lease manager needs to contact the sponsor in the client's application domain. In other words, the lease manager will act like a client, and the sponsor will act like a server. For this reason, you need to modify the configuration file to use bidirectional channels: <channel ref="tcp" port="0" /> When running the application, notice that the lease time actually dips below 0, into negative numbers. This is because the lease has expired but the lease sponsor hasn't yet been contacted. After the lease sponsor has been contacted, it responds with a renewal request and the time is reset to 5 minutes. If necessary, the client can also retain a reference to the sponsor, which allows it to tweak the RenewalTime (for example, making it longer if the remote object is holding onto expensive server resources that could be time consuming to re-create). To switch off the sponsor, just unregister it: Sponsor.Unregister(RemoteObj) In version 1.1 of the .NET Framework, you need to explicitly allow full serialization in order to use a leasing sponsor. To do so, modify the channel portion of the client and server configuration file as shown here: <channels> <channel ref="tcp" port="8080" > <serverProviders> <formatter ref="binary" typeFilterLevel="Full" /> </serverProviders> </channel> Note Leases are efficient because they require very little network traffic. Objects that are relatively lightweight can have long lease times for convenience. Objects that require resources that are expensive to re-create might also use a longer lease time or make use of a larger RenewOnCallTime to ensure that they are always retained if they are in use. (This provides a primitive type of caching.) On the other hand, objects that encapsulate important server resources or consume a large amount of memory will generally have short lease times, ensuring that they can be released as soon as the client has finished with them. In this case, you trade increased network traffic for more efficient use of server resources. |