在ASPNET MVC SinglePageApplication中缓存数据

时间:2014-07-21 08:17:28

标签: ajax asp.net-mvc session single-page-application

我正在寻找有关我的问题的解决方案或最佳做法。因为它更像是一种架构挑战,所以我没有源代码......

我的问题是我如何处理有关我的要求的ASP.NET会话。或者它不符合我的要求?

情况:我在ASP.NET MVC的顶部有一个SinglePageApplication构建。 MVC-app提供服务器端接口来处理后端。 MVC-app调用不同后端服务器的服务来请求数据。这些数据可能很多,应该在MVC-app中缓存。

据我所知,ASP.NET会话似乎不是SPA的最佳解决方案,因为它的并发行为......?!如果有任何请求需要对会话进行写访问,那么就不可能有并发的ajax请求(这种情况发生在我的情况下,因为数据必须被缓存)。

示例:至少在我的应用程序中,典型情况是当用户访问某个屏幕时,会执行一些ajax请求。例如。第一个请求触发从后端服务器加载数据并花费一些时间(可能大约30秒......)。它还需要对会话进行写访问,因为它应该缓存此(!)用户的数据。其他请求现在必须等待,即使它们只需要对会话的读访问权限并且不处理要加载的数据。一个请求等待另一个请求的行为应该只在某些情况下发生。

我现在可以使用 System.Runtime.Caching 为每个用户实现一种不同的缓存数据的机制,但这是推荐的还是最佳解决方案?

处理这种情况的最佳模式是什么?

有没有最佳做法?

EDIT2:

一个重点是执行并发ajax调用时会话的行为。我准备了一个小例子:一个执行ajax调用的应用程序 - 有些人正在使用会话作为ReadOnly - 其他人需要ReadWrite。这些操作超时,问题更加明显......

这些是我的控制者:

[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
public class SessionReadOnlyController : Controller
{
    public ActionResult Perform()
    {
        Session["x"] = 5;

        Thread.Sleep(1500);
        return Json(new { success = true }, JsonRequestBehavior.AllowGet);
    }
}

[SessionState(System.Web.SessionState.SessionStateBehavior.Required)]
public class SessionReadWriteController : Controller
{
    public ActionResult Perform()
    {
        Session["x"] = 5;

        Thread.Sleep(1500);
        return Json(new { success = true }, JsonRequestBehavior.AllowGet);
    }
}

这是执行ajax调用的javascript代码。所有电话都会立即执行:

jQuery(function ()
{
    $('body').append('<p>start....1</p>');
    $.ajax({
        url: '/SessionReadWrite/Perform?id=1',
        success: function () { $('body').append('<p>success1</p>'); }
    });

    $('body').append('<p>start....2</p>');
    $.ajax({
        url: '/SessionReadWrite/Perform?id=2',
        success: function () { $('body').append('<p>success2</p>'); }
    });

    $('body').append('<p>start....3</p>');
    $.ajax({
        url: '/SessionReadOnly/Perform?id=3',
        success: function () { $('body').append('<p>success3</p>'); }
    });

    $('body').append('<p>start....4</p>');
    $.ajax({
        url: '/SessionReadOnly/Perform?id=4',
        success: function () { $('body').append('<p>success4</p>'); }
    });

    $('body').append('<p>start....5</p>');
    $.ajax({
        url: '/SessionReadOnly/Perform?id=5',
        success: function () { $('body').append('<p>success5</p>'); }
    });
});

我的案例中的结果(取自互联网资源管理器的开发者工具):

  • 在1,51s
  • 之后,呼叫1响应
  • 呼叫2在3,03s后回复
  • 呼叫3在4,56s后回复
  • 4,56s后呼叫4响应
  • Call 5在4,56s之后回应

据我了解情况的行为,即使对于ReadOnly-Session-actions,ReadWrite-Session-actions也会锁定会话......?! (或者我做错了什么?)

来自msdn(http://msdn.microsoft.com/de-de/library/ms178581%28v=vs.100%29.aspx),最后一章:

  

[...]但是,如果对同一会话发出两个并发请求(通过使用相同的SessionID值),则第一个请求将获得对会话信息的独占访问权。第二个请求仅在第一个请求完成后执行。 [...]但是,会话数据的只读请求可能仍然需要等待会话数据的读写请求设置的锁定才能清除。

关于我的情况:如果我想在SinglePageApplication中缓存数据(内存中是好的,因为应用程序仅由少数用户使用),我需要一个与会话不同的概念...因此我的问题,是否存在最佳做法?

编辑:改进了对情况的描述......

Edit2:添加了一个示例......

1 个答案:

答案 0 :(得分:2)

现在,最后,问题很明显。由于ASP.NET从不同的线程锁定同一用户的会话访问权限,因此Session不允许您解决问题。

因此,您需要自己实现一些线程安全的东西。正如你在问题中所说,没有很多人,并且内存消耗不是问题,你可以使用自定义静态类,其生命周期从创建(第一次访问)到结束或应用程序(例如服务器重启) ,或应用程序池回收)。

您必须确定是为用户还是为会话存储数据。如果它是为用户存储的,它将会生存下来。会话超时。如果它是由会话ID标识的会话存储的,则您可以在会话结束时在global.asax中处理会话结束事件。

您需要两个级别的锁定:

  1. 一个用于访问特定用户的数据和锁定(或创建一个新用户并将其返回)。
  2. 其他用户的锁和数据(如上所述)
  3. 对于1,您可以使用类似ConcurrentDictionary的内容。在此字典中,保留用户ID +用户的相关锁和数据。每当您需要为用户读取或写入数据时,请从此字典中获取数据。第一级或锁定保证访问用户的锁和数据而不受其他线程的干扰。您可以使用GetOrAdd方法。

    对于2,您可以使用具有此类成员的对象,这将是提供用户ID时从字典返回的对象:

    private object _data; // to store the cached data
    
    public void StoreData(object data)
    {
        // - If there is a lock return inmediately: 
        //   someone is already writing the data
        // - If there is no lock, create the lock, store the data in _data, 
        //   and release the lock
    }
    
    public object GetData(int userId)
    {
        // - Wait for the lock
        // - Return data from _data
    }
    

    有许多方法可以锁定对_data的访问权限。我使用事件等待句柄:see here for a full explanation。但是还有更多有效的选择。

    通过此实现,

    • 任何尝试读取或写入的线程都将从字典1中获取用户数据。GetOrAdd的此锁定将非常短。第一个访问字典将创建对象(2)。下一个线程将恢复相同的对象(2)。
    • 如果线程正在尝试读取数据,它将使用object(2)GetData方法,该方法将锁定直到有可用数据(实际上,它可以读取空数据,并触发{ {1}}并递归调用StoreData以保证数据的创建。 *如果线程正在尝试写入数据,它将使用将创建锁的对象(2)GetData方法,并在写入时释放它

    为了做一个好的实现,你需要接收一个返回数据的函数,这样数据的创建也受到锁的保护,即使用这样的方法,而不是{{1} }:

    StoreData

    您的代码必须这样做:

    StoreData

    锁定不是一项简单的任务,一项非常非常重要的事情是保证任何已建立的锁定始终发布。您可以使用try-finally模式来执行此操作。