0

我正在使用.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');
        });
    });
});
4

2 回答 2

4

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

方面HttpContext.Session

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

就不同请求的 Session 而言:

在 ASP.Net Core 中,Session来自ISessionStore,它有一个短暂的生命周期。意思是,Session对象不被请求共享。因此,如果您有并发请求,每个请求都会有自己的Session对象。

在比赛条件方面:

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

因为是每个客户端,所以当您有来自同一个客户端的并发请求在相同的 cookie/会话状态中触及相同的位和片段时Session,您可能会遇到竞争条件。然而,竞争条件不是因为在服务器端。它实际上是由客户端的 cookie 管理引起的。Session

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

考虑这个例子:

假设您有一个设置Session为 provided的控制器操作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"}

在上述情况下,请求 3创建获取请求 6之间更改了会话状态,这意味着请求 6可能无法正确查看其会话状态。

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

于 2020-04-12T10:30:44.097 回答
0

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

于 2020-04-11T18:18:49.910 回答