2015-04-08 39 views
2

我有一個用戶控件顯示來自數據庫的信息。這個用戶控制必須不斷更新這些信息(比如說每5秒鐘)。此用戶控件的幾個實例在運行期間以單個頁面的形式以編程方式生成。在這個用戶控件的後面代碼中,我添加了一個代碼,它向數據庫發送查詢以獲取所需的信息(這意味着用戶控件的每個實例都這樣做)。但是這似乎減慢了查詢的處理速度,所以我正在創建一個靜態類,它將查詢信息並將其存儲在其變量中,並讓我的用戶控件的實例訪問這些變量。現在我需要這個靜態類來每5秒鐘執行一次查詢來更新它的變量。我嘗試使用一個新的線程來做到這一點,但變量似乎並沒有更新,因爲每當我從不同的類訪問它們時,我總是得到一個NullReferenceException。如何讓靜態類不斷更新自己的變量?

這裏是我的靜態類:

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); 
     } 
    } 

而且這是我正在試圖訪問我的靜態類變量:

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); 
     } 
    } 

任何幫助,請?由於

+0

mmm ...我懷疑只要初始化函數完成,線程t就會被殺死,因爲t的範圍是函數。你可能想要像singleton模式。 –

+0

我猜這裏沒有人注意到你正在運行越來越多的後臺線程 - 每次調用你的'Initialize'頁面並啓動一個新線程(並且不要告訴我你打算告訴所有用戶永不刷新該頁面,或者如果另一個用戶正在使用該應用程序,則不使用該應用程序,直到IIS回收所有內容)。你最終會遇到一堆線程,而你沒有互斥/監視器/鎖。我不確定'volatile'可以將線程分配給靜態字段之外的其他線程沒有觀察到已更改的值並體驗到'null'時檢測我的編輯 –

回答

2

這有幾個方法解決: 其中之一是:

這是更好地在Global.asax中的Application_Start或在session_start(取決於你的情況下)創建線程調用你的方法:

使用以下代碼:

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

第二種解決方案是使用Job Scheduler for ASP.NET(這是我的理想解決方案)。 獲取更多信息,您可以檢查此鏈接How to run Background Tasks in ASP.NET

和第三種解決方案是重寫靜態類如下:

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); 
    } 
} 
+0

謝謝。肯定會嘗試這個並相應地標記你的答案。對不起,上面的錯誤。 :) – Rian

+0

Thats ok @Rian :) – Peyman

3

多線程問題

您有獲取每創建一個線程並致電SessionManager.Initialize

這個過程在整個生命週期中發生的次數不止一次。 IIS在某個時間點回收您的應用程序,經過一段時間後應該完全沒有請求。 在此之前,您創建的所有線程都會繼續運行。

在第一個PageLoad之後,您將有一個線程每隔5秒更新一次。

如果您再次刷新頁面,您將有兩個線程,可能在時間上有不同的偏移量,但每個線程都以5秒爲間隔執行相同的操作。

您應該原子地檢查您的後臺線程是否已經啓動。您至少需要額外的布爾型靜態字段和對象靜態字段,您應該使用類似於Monitor(使用lock關鍵字)的靜態字段。

您還應該停止依賴易失性,並簡單地使用lock來確保其他線程「觀察」您的靜態List<..>字段的更新值。

可能是其他線程沒有觀察到更改字段,因此對於他們來說,該字段仍然是null - 因此您將獲得NullReferenceException

關於揮發性

使用volatile是壞,至少在.NET。有90%的可能性,你認爲你知道它在做什麼,這是不正確的,並有99%的機會讓你感到安心,因爲你使用了volatile,而且你沒有按照你應該的方式檢查其他多任務危害。

RX救援

我強烈建議你看看這個所謂的Reactive Extensions美妙的事情。

相信我,幾天的研究加上你處於一個完美的位置來使用RX的事實將會付出代價,而不僅僅是現在,但在將來也是如此。

你可以保留你的靜態類,但不是在該類中存儲的物化值,而是創建包含信息的管道。信息在您想要流動時流動。您將有訂戶到那些管道。用戶數量不會影響您的應用的整體性能。

您的應用將具有更高的可擴展性和更強大的功能。

祝你好運!

+0

我同意RX&MEF是非常棒的框架,並且有更靈活的應用程序。但我想這種方法,RX使代碼更復雜,並使用作業從數據庫中獲取數據在特定的時間框架解決問題(這只是我的想法) – Peyman

+0

@Peyman - 不要誤會我 - 我沒有提供代碼,你有,很感激。我的想法還包括你的'Task.Factory.StartNew'。我提議的僅僅是一個流動事件的現代分配系統 - 我實際上並沒有說任何有關Job/Scheduler的事情。 –

+0

好的@Eduard,對不起,我以爲你建議他使用RX作爲解決方案 – Peyman

1

這是一種方法上的改變,但我在Web窗體中保留了解決方案,使它更直接適用於您的用例。

SignalR是一種支持服務器和客戶端(瀏覽器)之間實時,雙向通信的技術,它可以替代您的靜態會話數據類。下面,我已經實現了一個簡單的例子來展示這個概念。

作爲示例,創建一個新的ASP.NET Web窗體應用程序並從nuget添加SignalR包。

Install-Package Microsoft.AspNet.SignalR 

您將需要添加一個新的Owin Startup class並添加這兩條線:

using Microsoft.AspNet.SignalR; 

...和方法中

app.MapSignalR(); 

添加一些UI元素Default.aspx

<div class="jumbotron"> 

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

    <p class="stats"> 

    </p> 

</div> 

將以下JavaScript添加到Site.Master。此代碼引用signalr,並實現客戶端事件處理程序並從瀏覽器啓動與信號集線器的聯繫。下面的代碼:

<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> 

最後,添加一個SignalR Hub到解決方案和使用的SessionDataHub執行這段代碼:

[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); 
    } 
} 

有關SignalR的更多信息,請訪問http://asp.net/signalr

鏈接源代碼:https://lsscloud.blob.core.windows.net/downloads/WebApplication1.zip

+1

看起來很腫。這將成爲我從現在開始的SignalR的備忘錄:)儘管我使用MVC :) –

+0

與MVC一起使用的代碼基礎非常類似(代替Site.Master用於_Layout.cshtml等) –

+0

我以前使用過SignalR但不是一段時間..我不知道他們做了這些美妙的'動態'expando透明代理服務器(我猜這就是服務器端的'Clients.All.someoneJoined')。它看起來很棒:)。我不認爲SignalR從那裏得到了這樣的結果 –