【问题标题】:Is something along the lines of nested memoization needed here?这里需要嵌套记忆的东西吗?
【发布时间】:2010-05-05 22:10:55
【问题描述】:

众所周知,System.Transactions 会将涉及到同一数据库的多个连接的事务升级到 DTC。下面的模块和帮助器类ConnectionContext 旨在通过确保对同一数据库的多个连接请求返回相同的连接对象来防止这种情况。从某种意义上说,这就是记忆,尽管有很多东西被记忆,第二个依赖于第一个。有没有办法在这个模块中隐藏同步和/或可变状态(可能使用记忆),或者用更实用的风格重写它?

(通过连接字符串获取连接时没有锁定可能一文不值,因为 Transaction.Current 是ThreadStatic。)

type ConnectionContext(connection:IDbConnection, ownsConnection) =
    member x.Connection = connection
    member x.OwnsConnection = ownsConnection
    interface IDisposable with
        member x.Dispose() = if ownsConnection then connection.Dispose()

module ConnectionManager =
    let private _connections = new Dictionary<string, Dictionary<string, IDbConnection>>()

    let private getTid (t:Transaction) = t.TransactionInformation.LocalIdentifier

    let private removeConnection tid =
        let cl = _connections.[tid]
        for (KeyValue(_, con)) in cl do
            con.Close()
        lock _connections (fun () -> _connections.Remove(tid) |> ignore)

    let getConnection connectionString (openConnection:(unit -> IDbConnection)) =
        match Transaction.Current with
        | null -> new ConnectionContext(openConnection(), true)
        | current ->
            let tid = getTid current

            // get connections for the current transaction
            let connections = 
                match _connections.TryGetValue(tid) with
                | true, cl -> cl
                | false, _ -> 
                    let cl = Dictionary<_,_>()
                    lock _connections (fun () -> _connections.Add(tid, cl))
                    cl

            // find connection for this connection string
            let connection =
                match connections.TryGetValue(connectionString) with
                | true, con -> con
                | false, _ ->
                    let initial = (connections.Count = 0)
                    let con = openConnection()
                    connections.Add(connectionString, con)
                    // if this is the first connection for this transaction, register connections for cleanup
                    if initial then 
                        current.TransactionCompleted.Add 
                            (fun args -> 
                                let id = getTid args.Transaction
                                removeConnection id)
                    con

            new ConnectionContext(connection, false)

【问题讨论】:

    标签: f# memoization system.transactions threadstatic


    【解决方案1】:

    是的,它看起来有点像记忆化——记忆化总是必须在 F# 中使用突变来实现,所以原则上你使用可变集合这一事实不是问题。

    我认为您可以尝试通过在代码中查找重复模式来简化它。如果我理解的话,您的代码实际上实现了两级缓存,其中第一个键是事务 ID,第二个键是连接字符串。您可以尝试通过创建一个实现单级缓存的类型来简化它,然后通过两次嵌套缓存来组成您的事务管理器。

    我没有尝试在所有细节上重新实现它,但单级缓存可能如下所示:

    // Takes a function that calculates a value for a given 'Key
    // when it is not available (the function also gets a flag indicating
    // whether it is the first one, so that you can register it with transaction0
    type Cache<´Key, ´Value when ´Key : equality>(createFunc) =
      let dict = new Dictionary<´Key, ´Value>()
      // Utility function that implements global lock for the object
      let locked = 
        let locker = new obj()
        (fun f -> lock locker f)
    
      member x.Remove(key) = 
        locked (fun () -> dict.Remove(key))
    
      // Get item from the cache using the cache.Item[key] syntax
      member x.Item
        with get(key) = 
          match dict.TryGetValue(key) with
          | true, res -> res
          | false, _ ->
              // Calculate the value if it is not already available
              let res = createFunc (dict.Count = 0) key
              locked (fun () -> dict.Add(key, res))
              res
    

    现在,我认为您的TransactionManager 可以使用以下类型实现:

    Cache<string, Cache<string, Connection>>
    

    这将是对函数式编程必不可少的组合性原则的一个很好的使用。我想您可能需要使 Cache 类型更复杂一些(以便它在各种其他情况下调用您指定的函数,例如在删除值时),但原则上,您可以从尝试实现您的经理开始使用上面的类。

    【讨论】:

    • 这看起来是一个很好的起点。我经常发现,在使用可变状态时,我正在实现框架中已经存在的模式(例如,惰性、Seq.fold)。所以,我想看看这段代码中是否有我忽略的模式。
    • 我认为这在库中的任何地方都没有实现,但我认为与我的代码非常相似的东西出现在 Expert F# 中。在我的真实世界函数式编程中显示记忆化的示例将该模式简单地实现为一个带有函数的函数(它不允许删除记忆化的值)。我猜记忆化需要一些调整(例如,你如何存储值,如何删除它们,......),所以它没有内置类(因为它在许多情况下可能不适合需要)。
    【解决方案2】:

    我不清楚您使用什么标准来声明对此的“改进”。

    对我来说,它看起来可能是错误的;如果我使用相同的连接字符串在两个不同的线程(都没有 Transaction.Current)上调用getConnection,我会得到两个连接,对吗?或者也许这是设计使然,当 TLS 中已经存在 Transaction.Current 时,您只是试图“重用”连接?在那种情况下,您的字典似乎也可能是 ThreadStatic 并删除所有本地锁定?

    我想我想查看客户端代码和期望的客户端行为(实际的或理想化的)。

    【讨论】:

    • 是的,您注意到的是期望的行为。我没有使用 ThreadStatic 字典,因为 TransactionCompleted 事件在不同的线程上触发。 ConnectionManager.getConnection 从具有关联连接字符串的类似 DataProvider 的类中调用。 getConnection 的目标是在事务中调用时返回一个缓存的连接,否则返回一个新的连接(可能依赖连接池来提高效率)。有更好的方法吗?
    • 回复您的第一句话:我正在寻找代码中的模式,这些模式要么在框架中实现,要么有功能性习惯用法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-28
    • 2015-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多