【问题标题】:Added keys missing from HttpContext.Session (concurrency)添加了 HttpContext.Session 中缺少的键(并发)
【发布时间】:2020-04-11 17:56:41
【问题描述】:

我正在使用 .NET Core 3.1。我想知道HttpContext.Session.SetString(...) 是否线程安全?例如,客户端同时向同一个控制器操作 (Test) 发出两个请求。控制器向会话中添加一个字符串(参见下面的示例)。会话结束时是否会有两个、一个或零个键(例如,当我刷新页面时)?

public IActionResult Test()
{
    HttpContext.Session.SetString(Guid.NewGuid().ToString(), "test");
    return Ok();
}

我在使用 DevExtreme FileUploader 时将一些值保存到会话中。当我一次上传多个文件时,组件同时发出多个请求,最后,通常会话中缺少一些键。我认为有一些竞争条件正在发生。

添加:客户代码

我注意到只有当我使用method: 'POST' 时才会丢失会话密钥(只有 1 个密钥)。如果我使用method: 'GET',则有 3 个键(正确)。

$(document).ready(function () {
    var method = 'GET'; // works (3 keys)
    //var method = 'POST'; // doesn't work (1 key)

    $('#fire').on('click', function () {
        $.when(
            $.ajax({
                url: 'Session/Test',
                method: method,
                success: function(){
                    console.log('response', 1);
                }
            }),

            $.ajax({
                url: 'Session/Test',
                method: method,
                success: function(){
                    console.log('response', 2);
                }
            }),

            $.ajax({
                url: 'Session/Test',
                method: method,
                success: function(){
                    console.log('response', 3);
                }
            })
        ).then(function () {
            alert('Done');
        });
    });
});

【问题讨论】:

    标签: c# asp.net-core .net-core devextreme


    【解决方案1】:

    假设您使用 ASP.Net Core 提供的默认会话实现。

    HttpContext.Session而言:

    HttpContext.Session 返回一个DistributedSession 的实例,它在内部使用Dictionary<TKey, TValaue>。字典不是线程安全的,所以如果你从多个线程(例如Task.Run)访问Session,它可能会导致意想不到的结果。

    就不同请求的 Session 而言:

    在 ASP.Net Core 中,Session 来自 ISessionStore,其中有一个 transient lifetime。意思是,Session 对象不被请求共享。因此,如果您有并发请求,每个请求都会有自己的 Session 对象。

    就比赛条件而言:

    会话的默认实现从.AspNetCore.Session cookie 读取/写入会话状态。这可能会导致竞争条件。

    因为Session 是针对每个客户端的,所以您可能会遇到when you have concurrent requests from the same client touching same bits and pieces in the same cookie / session state 的竞争条件。然而,竞争条件不是因为服务器端的Session。这实际上是由客户端的cookie管理引起的。

    会话状态是非锁定的。如果两个请求同时尝试修改会话的内容,则最后一个请求会覆盖第一个请求。

    考虑这个例子:

    假设您有一个控制器操作将Session 设置为提供的value,另一个控制器操作从Session 检索值并将其返回到正文中:

    [HttpGet]
    [Route("create")]
    public IActionResult CreateSession([FromQuery]string value)
    {
        HttpContext.Session.SetString("key", value);
        return Ok();
    }
    
    [HttpGet]
    [Route("get")]
    public IActionResult ReturnSession([FromQuery] string expected)
    {
        var actual = HttpContext.Session.GetString("key");
        return Ok(new { actual, expected });
    }
    

    如果您使用HttpClient 测试这些操作:

    async Task TestSession(HttpClient client, string str)
    {
        await client.GetAsync($"https://localhost:5001/session/create?value={str}");
        var r = await client.GetAsync($"https://localhost:5001/session/get?expected={str}");
        var session = await r.Content.ReadAsStringAsync();
    
        Console.WriteLine(session);
    }
    
    using (var client = new HttpClient())
    {
        await TestSession(client, "abc");
    }
    

    输出应如下所示:

    {"actual":"abc","expected":"abc"}
    

    当您有来自同一客户端的并发请求时会引发问题:

    using (var client = new HttpClient())
    {
        var tasks = new List<Task>();
        for (var i = 0; i < 10; i++)
        {
            var str = i.ToString();
            tasks.Add(Task.Run(() => TestSession(client, str)));
        }
    
        await Task.WhenAll(tasks);
    }
    

    输出如下:

    {"actual":"2","expected":"1"}
    {"actual":"3","expected":"6"}
    {"actual":"4","expected":"5"}
    {"actual":"4","expected":"3"}
    {"actual":"4","expected":"2"}
    {"actual":"8","expected":"4"}
    {"actual":"7","expected":"8"}
    {"actual":"7","expected":"7"}
    {"actual":"9","expected":"0"}
    {"actual":"9","expected":"9"}
    

    在上述情况下,request 3request 6createget 之间更改了会话状态>,这意味着它很可能 request 6 无法正确查看其会话状态。

    为避免此问题,您可以为每个批次使用不同的HttpClient

    【讨论】:

    • 您能否澄清一下您所说的“如果缺少某些键,请确保在会话更新后包含正确的 cookie。”?如何确保包含正确的 cookie?
    • @Mark Session 针对每个客户设计。如果您从 same 客户端发出多个请求,它们可能共享同一个 cookie 容器,因此共享相同的会话状态。我不确定您所说的 some keys missing from session 是什么意思。但是,如果您有来自同一个客户端的两个请求都触发服务器更新会话中的相同字段,则很可能只有 1 个请求获胜。对于另一个请求,会话丢失/被覆盖。
    • 我在控制器中声明了private static int count = 0;,并在每次向会话中添加一些内容时增加它的值(注意:添加的键肯定不同 - 我使用了 GUID)。然后,我发送了 3 个并发请求。当我刷新页面时,count 的值为 3。但是,会话中只有 1 个键。是什么原因造成的?
    • 我用 JS 代码更新了我原来的问题。我发现POSTGET 之间有一个有趣的区别。请看一下。
    • GET 并不总是适合我。我在服务器端发起了 3 个 get 调用,每个调用最初在 Session 中都没有键。然后服务器执行请求并设置会话密钥。现在我又打了 3 个电话。我在每个请求中只看到 Session 中的 1 个键。 Post 的行为相同。
    【解决方案2】:

    不,dotnet 集合不是线程安全的,会话也不是。 如果您在多个线程上共享同一会话,则应将其视为只读

    【讨论】:

    • 您能否提供任何参考资料让我可以验证此信息?
    猜你喜欢
    • 2020-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-29
    • 2019-10-06
    • 1970-01-01
    • 1970-01-01
    • 2021-04-27
    相关资源
    最近更新 更多