Руководители сеансов

Ожидаемое поведение кода ниже:

  • Будет существовать один статический SessionManager, к которому обращаются многие потоки.
  • Несколько вызовов на SessionManager.UseTheSession для данного идентификатора сеанса должны обрабатываться на основе первого запроса (аналогично, вызывая EndSession должен разрешить выполнение в текущий момент UseTheSession ).
  • Цель отдельных сеансовых блокировок состоит в том, что вызов SessionManager.UseTheSession одним потоком не задерживает другой поток, который вызывает тот же метод с другим идентификатором сеанса.

Вещи, которые я ищу для ввода:

  • Выполняется ли это так, как ожидалось.
  • sessions должен быть ConcurrentDictionary или обычный Dictionary работает отлично, поскольку я блокирую доступ к одному ключу.
  • Что такое хороший способ справиться с утечкой памяти, которая возникает, когда я продолжаю создавать новые блокировки, но не удаляю их для заданного идентификатора после того, как перестаю использовать его?

Код (выполняется в Linqpad с включением пространства имен System.Collections.Concurrent):

void Main()
{
    var sm = new SessionManager();

    Guid id = sm.StartSession();

    sm.UseTheSession(id).Dump();
    sm.UseTheSession(id).Dump();

    sm.EndSession(id);
}

public class SessionManager
{
    private ConcurrentDictionary<Guid, object> sessionLocks =
        new ConcurrentDictionary<Guid, object>();

    private ConcurrentDictionary<Guid, ASession> sessions =
        new ConcurrentDictionary<Guid, ASession>();

    public Guid StartSession()
    {
        Guid id = Guid.NewGuid();

        // Takes a sec to create the session.
        var session = new ASession(string.Format("Session {0}", id));
        Thread.Sleep(1000);

        if(!sessions.TryAdd(id, session))
            throw new Exception("Astronomically unlikely situation encountered.");

        return id;
    }

    public int UseTheSession(Guid id)
    {
        lock(this.GetSessionLocker(id))
        {
            ASession session;
            if(sessions.TryGetValue(id, out session))
            {
                return this.DoSomethingWithSession(session);
            }
            else
            {
                throw new Exception(string.format("Session with id {0} does not exist.",
                    id));
            }
        }
    }

    public void EndSession(Guid id)
    {
        lock(this.GetSessionLocker(id))
        {
            ASession removedSession;
            if(sessions.TryRemove(id, out removedSession))
            {
                this.CleanUpSessionRemnants(removedSession);
            }
        }
    }

    private object GetSessionLocker(Guid id)
    {
        return sessionLocks.GetOrAdd(id, x => new object());
    }

    private int DoSomethingWithSession(ASession session)
    {
        Thread.Sleep(1000);

        return 1;
    }

    private void CleanUpSessionRemnants(ASession session)
    {
        Thread.Sleep(1000);
    }
}

public class ASession
{
    public ASession(string name)
    {
        this.Name = name;
    }

    public string Name { get; private set; }
}
11 голосов | спросил Ocelot20 29 MaramSat, 29 Mar 2014 01:55:06 +04002014-03-29T01:55:06+04:0001 2014, 01:55:06

2 ответа


5

Несколько примечаний для решения ваших конкретных вопросов:

  

Выполняется ли это так, как ожидалось?

Только вы можете ответить на это после тщательного тестирования.

  

Должны ли сеансы быть ConcurrentDictionary или обычный словарь работает здесь, так как я блокирую любой доступ к одному ключу.

Поскольку клавиши блокировки различны для разных сеансов, гораздо безопаснее использовать ConcurrentDictionary. Таким образом, вы, по крайней мере, уверены, что словарь не испортится.

Однако ваш подход к карте sessionLocks У меня есть некоторые сомнения. То, что вы действительно хотите синхронизировать, это return this.DoSomethingWithSession(session); и this.CleanUpSessionRemnants(removedSession);, поэтому я бы разместил блокировку только вокруг них.

Говоря о размещении замков, рассмотрите возможность размещения их в классе ASession. На самом деле, я бы попытался переместить CleanUpSessionRemnants и DoSomethingWithSession для класса ASession. Если для выполнения своего задания нужен объект SessionManager, затем передайте SessionManager в качестве параметра для метода. Прямо сейчас ваш ASession кажется только String, лучше использовать этот класс. Я думаю, вы создали его по какой-то причине.

  

Каков хороший способ справиться с утечкой памяти, которая возникает, когда я продолжаю создавать новые блокировки, но не удаляю их для заданного идентификатора после того, как перестаю использовать его?

Не создавайте их в первую очередь. В самом деле. Или, по крайней мере, удалите их, когда закончите. См. Выше. Или код ниже.

if(sessions.TryRemove(id, out removedSession))
{
    sessionLocks.TryRemove(id);
    this.CleanUpSessionRemnants(removedSession);
}
ответил Simon Forsberg 29 MaramSat, 29 Mar 2014 03:40:40 +04002014-03-29T03:40:40+04:0003 2014, 03:40:40
5

В этом ответе Подробно объясню, почему нельзя бросать System.Exception . Вы должны бросить InvalidOperationException в случае астрономически маловероятной ситуации и, возможно, ArgumentException в случае несуществующего сеанса.

Что касается безопасности потоков, я не часто пишу многопоточный код, поэтому я мог бы быть совершенно неправильным, но с тех пор, как ConcurrentDictionary представляет собой потокобезопасную реализацию IDictionary, sessions.TryGetValue(id, out session) уже является поточно-безопасным вызовом, его не нужно обертывать в блок lock.

ответил Mathieu Guindon 29 MaramSat, 29 Mar 2014 03:08:50 +04002014-03-29T03:08:50+04:0003 2014, 03:08:50

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132