简体   繁体   中英

How to make a static class update its own variables constantly?

I have a user control that displays information from the database. This user control has to update these information constantly(let's say every 5 seconds). A few instances of this user control is generated programmatically during run time in a single page. In the code behind of this user control I added a code that sends a query to the database to get the needed information (which means every single instance of the user control is doing this). But this seems to slow down the processing of queries so I am making a static class that will do the querying and store the information in its variables and let the instances of my user control access those variables. Now I need this static class to do queries every 5 seconds to update its variables. I tried using a new thread to do this but the variables don't seem to be updated since I always get a NullReferenceException whenever I access them from a different class.

Here's my static class:

public static class SessionManager
{
    public static volatile List<int> activeSessionsPCIDs;
    public static volatile List<int> sessionsThatChangedStatus;
    public static volatile List<SessionObject> allSessions;

    public static void Initialize() {
        Thread t = new Thread(SetProperties);
        t.Start();
    }

    public static void SetProperties() {
        SessionDataAccess sd = new SessionDataAccess();
        while (true) {
            allSessions = sd.GetAllSessions();
            activeSessionsPCIDs = new List<int>();
            sessionsThatChangedStatus = new List<int>();
            foreach (SessionObject session in allSessions) {
                if (session.status == 1) { //if session is active
                    activeSessionsPCIDs.Add(session.pcid);
                }
                if (session.status != session.prevStat) { //if current status doesn't match the previous status
                    sessionsThatChangedStatus.Add(session.pcid);
                }
            }
            Thread.Sleep(5000);
        }
    }

And this is how I am trying to access the variables in my static class:

protected void Page_Load(object sender, EventArgs e)
    {
        SessionManager.Initialize();
        loadSessions();
    }

    private void loadSessions()
    { // refresh the current_sessions table
        List<int> pcIds = pcl.GetPCIds(); //get the ids of all computers
        foreach (SessionObject s in SessionManager.allSessions)
        {
            SessionInfo sesInf = (SessionInfo)LoadControl("~/UserControls/SessionInfo.ascx");
            sesInf.session = s;
            pnlMonitoring.Controls.Add(sesInf);
        } 
    }

Any help, please? Thanks

Multiple threads problem

You have one thread that gets created for each and every call to SessionManager.Initialize .

That happens more than once in the lifetime of the process. IIS recycles your app at some point, after a period of time should you have absolutely no requests. Until that happens, all your created threads continue to run.

After the first PageLoad you will have one thread which updates stuff every 5 seconds.

If you refresh the page again you'll have two threads, possibly with different offsets in time but each of which, doing the same thing at 5 second intervals.

You should atomically check to see if your background thread is started already. You need at least an extra bool static field and a object static field which you should use like a Monitor (using the lock keyword).

You should also stop relying on volatile and simply using lock to make sure that other threads "observe" updated values for your static List<..> fields.

It may be the case that the other threads don't observe a change field and thusly, for them, the field is still null - therefore you get the NullReferenceException .

About volatile

Using volatile is bad, at least in .NET. There is a 90% chance that you think you know what it is doing and it's not true and there's a 99% chance that you feel relief because you used volatile and you aren't checking for other multitasking hazards the way you should.

RX to the rescue

I strongly suggest you take a look at this wonderful thing called Reactive Extensions .

Believe me, a couple of days' research combined with the fact that you're in a perfect position to use RX will pay of, not just now but in the future as well.

You get to keep your static class, but instead of materialised values that get stored within that class you create pipes that carry information. The information flows when you want it to flow. You get to have subscribers to those pipes. The number of subscribers does not affect the overall performance of your app.

Your app will be more scalable, and more robust.

Good luck!

There are few solution for this approach: One of them is:

It's better in Global.asax in Application_start or Session_Start (depends on your case) create Thread to call your method:

Use below code :

var t = Task.Factory.StartNew(() => {
    while(true)
    {
      SessionManager.SetProperties();
      Task.Delay(5);
    }
});

Second solution is using Job Scheduler for ASP.NET (that's my ideal solution). for more info you can check this link How to run Background Tasks in ASP.NET

and third solution is rewrite your static class as follow:

public static class SessionManager
{
 public static volatile List<int> activeSessionsPCIDs;
 public static volatile List<int> sessionsThatChangedStatus;
 public static volatile List<SessionObject> allSessions;

 static SessionManager()
 {
   Initialize();
 }

public static void Initialize() {
    var t = Task.Factory.StartNew(() => {
       while(true)
       {
         SetProperties();
         Task.Delay(5);
       }
     });
}

public static void SetProperties() {
    SessionDataAccess sd = new SessionDataAccess();
    while (true) {
        allSessions = sd.GetAllSessions();
        activeSessionsPCIDs = new List<int>();
        sessionsThatChangedStatus = new List<int>();
        foreach (SessionObject session in allSessions) {
            if (session.status == 1) { //if session is active
                activeSessionsPCIDs.Add(session.pcid);
            }
            if (session.status != session.prevStat) { //if current status doesn't match the previous status
                sessionsThatChangedStatus.Add(session.pcid);
            }
        }
        Thread.Sleep(5000);
    }
}

This is a solution that is a change in approach, but I kept the solution in Web Forms, to make it more directly applicable to your use case.

SignalR is a technology that enables real-time, two way communication between server and clients (browsers), which can replace your static session data class. Below, I have implemented a simple example to demonstrate the concept.

As a sample, create a new ASP.NET Web Forms application and add the SignalR package from nuget.

Install-Package Microsoft.AspNet.SignalR

You will need to add a new Owin Startup class and add these 2 lines:

using Microsoft.AspNet.SignalR;

... and within the method

app.MapSignalR();

Add some UI elements to Default.aspx :

 <div class="jumbotron">

    <H3 class="MyName">Loading...</H3>

    <p class="stats">

    </p>

</div>

Add the following JavaScript to the Site.Master . This code references signalr, and implement client-side event handlers and initiates contact with the signalr hub from the browser. here's the code:

<script src="Scripts/jquery.signalR-2.2.0.min.js"></script>
<script src="signalr/hubs"></script>
<script >
    var hub = $.connection.sessiondata;
    hub.client.someOneJoined = function (name) {
        var current = $(".stats").text();
        current = current + '\nuser ' + name + ' joined.';
        $(".stats").text(current);
    };
    hub.client.myNameIs = function (name) {
        $(".MyName").text("Your user id: " + name);
    };

    $.connection.hub.start().done(function () { });
</script>

Finally, add a SignalR Hub to the solution and use this code for the SessionDataHub implementation:

[HubName("sessiondata")]
public class SessionDataHub : Hub
{
    private ObservableCollection<string> sessions = new ObservableCollection<string>();

    public SessionDataHub()
    {
        sessions.CollectionChanged += sessions_CollectionChanged;
    }

    private void sessions_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            Clients.All.someOneJoined(e.NewItems.Cast<string>().First());
        }
    }

    public override Task OnConnected()
    {
        return Task.Factory.StartNew(() =>
        {
            var youAre = Context.ConnectionId;
            Clients.Caller.myNameIs(youAre);
            sessions.Add(youAre);
        });
    }

    public override Task OnDisconnected(bool stopCalled)
    {
        // TODO: implement this as well. 
        return base.OnDisconnected(stopCalled);
    }
}

For more information about SignalR, go to http://asp.net/signalr

Link to source code: https://lsscloud.blob.core.windows.net/downloads/WebApplication1.zip

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM