2015-04-08 3 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 относится к функции. вы, вероятно, хотите что-то вроде синглтон-паттерна. –

+0

Я предполагаю, что никто здесь не заметил, что вы используете растущее число фоновых потоков - при каждой загрузке страницы вы вызываете «Initialize» и запускаете новый поток (и не говорите мне, что вы намерены рассказать всем своим пользователям никогда не обновлять страницу или не использовать приложение, если другой пользователь использует его, пока IIS не переработает все). Вы получаете беспорядок потоков, и у вас нет мьютексов/мониторов/замков. Я не уверен, что 'volatile' избавляет вас от ситуации, когда потоки, отличные от тех, которые присваивают значения статическим полям, не замечают измененного значения и не испытывают« null »- проверьте мое редактирование –

ответ

2

Есть несколько решения для такого подхода: Один из них:

Это лучше в Global.asax в Application_Start или session_start (зависит от Вашего случая) создать тему для вызова метода:

Используйте нижеприведенный код:

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

Второе решение использует Планировщик заданий для 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 секунд.

Вы должны atomically проверить, не начата ли ваша фоновая нить. Вам нужно как минимум дополнительное bool статическое поле и объект статическое поле, которое вы должны использовать как Monitor (используя ключевое слово lock).

Вы также должны прекратить полагаться на volatile и просто использовать lock, чтобы убедиться, что другие потоки «наблюдают» обновленные значения для ваших статических полей List<..>.

Возможно, что другие нити не соблюдают поле смены, и для них поле остается null - поэтому вы получаете NullReferenceException.

О летучего

Использование volatile плохо, по крайней мере, в .NET. Вероятно, 90% вероятность того, что вы знаете, что он делает, и это неправда, и есть 99% шанс, что вы чувствуете облегчение, потому что вы использовали volatile, и вы не проверяете другие опасности многозадачности так, как вам следует.

RX на помощь

я настоятельно рекомендую вам взглянуть на эту замечательную вещь под названием Reactive Extensions.

Поверьте, исследование нескольких дней в сочетании с тем фактом, что вы находитесь в идеальном положении для использования RX, будет оплачиваться не только сейчас, но и в будущем.

Вы можете сохранить свой статический класс, но вместо материализованных значений, которые хранятся в этом классе, вы создаете трубы, которые несут информацию. Информация течет, когда вы хотите ее протекать. У вас есть подписчиков для этих труб. Количество подписчиков не влияет на общую производительность вашего приложения.

Ваше приложение будет более масштабируемым и более надежным.

Удачи вам!

+0

Я согласен RX & MEF - это потрясающая структура и помощь в более гибком приложении. Но я предполагаю, что для этого подхода RX делает код более сложным и использует задание для извлечения данных из БД в определенные временные рамки, решая проблему (это только моя идея) – Peyman

+0

@ Пейман - Не поймите меня неправильно - я не предоставил код, у вас есть и это ценится. Моя идея включает и ваш 'Task.Factory.StartNew'. То, что я предлагаю, - это просто современная система распространения текущих событий - я ничего не говорю о Job/Scheduler. –

+0

Ok @Eduard, извините, я думал, что вы предлагаете ему использовать RX в качестве решения – Peyman

1

Это решение, которое является изменением подхода, но я сохранил решение в Web Forms, чтобы сделать его более непосредственно применимым к вашему прецеденту.

SignalR - это технология, обеспечивающая двустороннюю связь между сервером и клиентами (браузерами) в реальном времени, которая может заменить ваш статический класс данных сеанса. Ниже я представил простой пример для демонстрации концепции.

В качестве примера создайте новое приложение ASP.NET Web Forms и добавьте пакет SignalR из nuget.

Install-Package Microsoft.AspNet.SignalR 

Вам нужно будет добавить новый Owin Startup class и добавить эти 2 строки:

using Microsoft.AspNet.SignalR; 

... и в методе

app.MapSignalR(); 

Добавить некоторые элементы пользовательского интерфейса к Default.aspx:

<div class="jumbotron"> 

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

    <p class="stats"> 

    </p> 

</div> 

Добавьте следующий код JavaScript к Site.Master. Этот код ссылается на signalr и реализует обработчики событий на стороне клиента и инициирует контакт с концентратором 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 получил это от получения –

Смежные вопросы