使用 C# 实现控制台应用程序的会话超时

发布于 2024-11-26 12:01:11 字数 1588 浏览 3 评论 0原文

我正在用 C# 为我的控制台应用程序实现超时。 我有一个名为 MySession 的类,它必须受到会话超时的约束。这意味着在类的构造函数中,我为创建的对象配置了超时。如果在指定的超时时间内未调用类中的任何方法,则在随后访问任何方法时,都必须抛出 Timeout 异常。

这是该类的虚拟实现。

public class MySession {
private TimeSpan timeout;
private DateTime lastAccessedTime;

public MySession(TimeSpan timeout) {
    this.timeout = timeout;
    lastAccessedTime = DateTime.Now;
}

public void StoreName(string name) {
    TimeSpan diff = DateTime.Now - lastAccessedTime;
    if(diff.Ticks - timeout.Ticks > 0) {
        throw new TimeoutException("session timed out");
    }
    //store logic
    lastAccessedTime = DateTime.Now;
}

public void StoreAddress(string address) {
    TimeSpan diff = DateTime.Now - lastAccessedTime;
    if(diff.Ticks - timeout.Ticks > 0) {
        throw new TimeoutException("session timed out");
    }
    //store logic
    lastAccessedTime = DateTime.Now;
}

public void Commit() {
    TimeSpan diff = DateTime.Now - lastAccessedTime;
    if(diff.Ticks - timeout.Ticks > 0) {
        throw new TimeoutException("session timed out");
    }
    //commit logic
    lastAccessedTime = DateTime.Now;
}}

我有两个问题。

  1. 有没有更好的方法来避免在每个方法开始时进行检查?显然,我可以从该代码中重构 IsAlive 属性,但尽管如此,我仍然需要在每个方法的开头检查 IsAlive。 .NET 中是否有可以继承我的类的 Session 类?
  2. 假设,我说会话类中的一个方法返回另一个对象。从超时角度来看,对此对象的任何调用也需要被视为更新上次访问时间。对于多个嵌套对象,这很快就会变得非常复杂。但这只是一个假设的问题,也许是YAGNI;但我想知道是否有任何方法可以实现这一点。

编辑:(因为原始问题中没有提到这些点)

  1. Session 类确实有一个回滚方法。我只是没有添加它以保持代码较小!
  2. 存储实际上进入数据库事务范围内的后端数据库。因此,此类中的所有 Store 调用实际上都会将数据写入数据库,但只有在调用会话提交之后才需要在数据库上提交。

I am implementing a Timeout for my console application in C#.
I have a class called MySession which has to be bound by a Session timeout. This means that in the constructor of the class, I configure a timeout for the objects created. If any method in the class is not called within that specified timeout, then on subsequent access of any methods, a Timeout exception has to be thrown.

This is the dummy implementation of that class.

public class MySession {
private TimeSpan timeout;
private DateTime lastAccessedTime;

public MySession(TimeSpan timeout) {
    this.timeout = timeout;
    lastAccessedTime = DateTime.Now;
}

public void StoreName(string name) {
    TimeSpan diff = DateTime.Now - lastAccessedTime;
    if(diff.Ticks - timeout.Ticks > 0) {
        throw new TimeoutException("session timed out");
    }
    //store logic
    lastAccessedTime = DateTime.Now;
}

public void StoreAddress(string address) {
    TimeSpan diff = DateTime.Now - lastAccessedTime;
    if(diff.Ticks - timeout.Ticks > 0) {
        throw new TimeoutException("session timed out");
    }
    //store logic
    lastAccessedTime = DateTime.Now;
}

public void Commit() {
    TimeSpan diff = DateTime.Now - lastAccessedTime;
    if(diff.Ticks - timeout.Ticks > 0) {
        throw new TimeoutException("session timed out");
    }
    //commit logic
    lastAccessedTime = DateTime.Now;
}}

I have a two questions.

  1. Is there a better way to do this that avoids the checking that comes at the beginning of every method? I can obviously refactor a IsAlive property out of that code, but nonetheless I still need to check for IsAlive at the beginning of every method. Is there any Session class in .NET that I could inherit my class from?
  2. Hypothetically, let me say that one of my methods in the session class returns another object. Any calls on this object also needs to be considered as updating the lastaccessed time from a timeout perspective. This could soon get pretty complicated with multiple nested objects. But this is only a hypothetical question and maybe YAGNI; but I would like to know if there is any way this can be accomplished.

EDIT: (since these points were not mentioned in the original question)

  1. The Session class does indeed have a rollback method. I just did not add it to keep the code small!
  2. The Stores actually go to a backend database within the scope of a database transaction. So all Store calls in this class, would actually write the data to the database, but commit on the DB needs to happen only after commit on the session is called.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

笑叹一世浮沉 2024-12-03 12:01:11

首先,您应该考虑重新思考您的会话对象应该负责什么。通常,当我看到 Commit 方法时,我也会寻找 Rollback 方法,以确保如果操作失败,您的更新在某种程度上是一致的(尽管我是仍然不确定该类应该做什么)。

另外,如果 Commit 提交由 StoreSomething 方法添加的瞬态数据,那么我根本不明白为什么应该打开“会话”(同样,无论是什么)直到你决定真正承诺。

从长远来看,为问题添加更好的描述和一些背景可能会让我们提供更好的解决方案。

话虽如此,稍微重构的版本可能是:

  1. 首先定义一个接口(里氏替换原则):

    公共接口IMySession
    {
        void StoreName(字符串名称);
        void StoreAddress(字符串地址);
        无效提交();
    }
    
  2. 实现具有基本功能的最简单的“普通旧式”会话 (单一职责原则):

    公共类BasicSession:IMySession
    {
        #region IMySession 成员
    
        公共无效商店名称(字符串名称)
        {
            // 普通存储
        }
    
        public void StoreAddress(字符串地址)
        {
            // 普通存储
        }
    
        公共无效提交()
        {
            // 普通提交
        }
    
        #endregion
    }
    
  3. 最后创建一个代理类,检查超时情况,并将方法调用转发给基类类:

    公共类 TimeLimitedSessionProxy :IMySession
    {
        私有只读 IMySession _baseSession;
        私有只读TimeSpan_timeout;
        私有 DateTime _lastAccessedTime = DateTime.Now;
    
        公共 TimeLimitedSessionProxy(IMySession baseSession,TimeSpan 超时)
        {
            _baseSession = 基本会话;
            _timeout=超时;
        }
    
        #region IMySession 成员
    
        公共无效商店名称(字符串名称)
        {
            IfNotTimedOut(() => _baseSession.StoreName(名称));
        }
    
        public void StoreAddress(字符串地址)
        {
            IfNotTimedOut(() => _baseSession.StoreAddress(地址));
        }
    
        公共无效提交()
        {
            IfNotTimedOut(() => _baseSession.Commit());
        }
    
        #endregion
    
        私有无效IfNotTimedOut(操作操作)
        {
            if (DateTime.Now - _lastAccessedTime > _timeout)
            {
                throw new TimeoutException("会话超时");
            }
    
            行动();
    
            _lastAccessedTime = 日期时间.Now;
        }
    }
    

总体结果:

  • 其他您的代码的一部分应该接受一个 IMySession 对象,而不关心它在底层是如何实际实现的。

  • 即使是 TimeLimitedSessionProxy 也接受 IMySession 并且不知道实际的实现;它只关心时间。

  • 如果您决定添加其他功能,请考虑保留这些类不变并代理根据需要装饰它们。

Before anything else, you should consider rethinking what your session object should be responsible for. Usually, when I see a Commit method, I look for a Rollback method also, to make sure that your updates are in some way consistent if an operation fails (although I am still not sure what the class is supposed to do).

Also, if Commit commits transient data added by your StoreSomething methods, then I don't see why a "Session" (again, whatever that is) should be opened at all until you decide to actually commit.

Adding a better description and some background to the problem might allow us to provide a better solution in the long term.

Having said that, a slightly more refactored version might be:

  1. Start by defining an interface first (Liskov Substitution Principle):

    public interface IMySession
    {
        void StoreName(string name);
        void StoreAddress(string address);
        void Commit();
    }
    
  2. Implement a simplest "plain old" session with basic functionality (Single Responsibility Principle):

    public class BasicSession : IMySession
    {
        #region IMySession members
    
        public void StoreName(string name)
        {
            // plain store
        }
    
        public void StoreAddress(string address)
        {
            // plain store
        }
    
        public void Commit()
        {
            // plain commit
        }
    
        #endregion
    }
    
  3. Finally, create a proxy class, which checks for timeouts, and forwards method calls to the base class:

    public class TimeLimitedSessionProxy : IMySession
    {
        private readonly IMySession _baseSession;
        private readonly TimeSpan _timeout;
        private DateTime _lastAccessedTime = DateTime.Now;
    
        public TimeLimitedSessionProxy(IMySession baseSession, TimeSpan timeout)
        {
            _baseSession = baseSession;
            _timeout = timeout;
        }
    
        #region IMySession members
    
        public void StoreName(string name)
        {
            IfNotTimedOut(() => _baseSession.StoreName(name));
        }
    
        public void StoreAddress(string address)
        {
            IfNotTimedOut(() => _baseSession.StoreAddress(address));
        }
    
        public void Commit()
        {
            IfNotTimedOut(() => _baseSession.Commit());
        }
    
        #endregion
    
        private void IfNotTimedOut(Action action)
        {
            if (DateTime.Now - _lastAccessedTime > _timeout)
            {
                throw new TimeoutException("session timed out");
            }
    
            action();
    
            _lastAccessedTime = DateTime.Now;
        }
    }
    

Overall result:

  • Other parts of your code should accept an IMySession object, not caring how it's actually implemented under the hood.

  • Even the TimeLimitedSessionProxy accepts an IMySession and is ignorant about the actual implementation; all it cares about is timing.

  • If you decide to add other functionality, consider leaving these classes intact and proxying or decorating them as needed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文