交易范围类似的功能 [英] Transaction scope similar functionality

查看:93
本文介绍了交易范围类似的功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我希望设置与事务作用域非常相似的东西,该作用域在服务上创建一个版本,并将在作用域末尾删除/提交.在事务范围内运行的每个SQL语句都会在内部查看一些连接池/事务存储,以确定其是否在范围内并做出适当的反应.呼叫者不需要将事务传递给每个呼叫.我正在寻找此功能.

I am looking to setup something very similar to transaction scope which creates a version on a service and will delete/commit at the end of scope. Every SQL statement ran inside the transaction scope internally looks at some connection pool / transaction storage to determine if its in the scope and reacts appropriately. The caller doesn't need to pass in the transaction to every call. I am looking for this functionality.

有关此的更多信息:这是基本的一次性课程:

Here is the basic disposable class:

public sealed class VersionScope : IDisposable
{
    private readonly GeodatabaseVersion _version;
    private readonly VersionManager _versionManager;

    public VersionScope(Configuration config)
    {
        _versionManager = new VersionManager(config);
        _version = _versionManager.GenerateTempVersion();
        _versionManager.Create(_version);
        _versionManager.VerifyValidVersion(_version);
        _versionManager.ServiceReconcilePull();
        _versionManager.ReconcilePull(_version);
    }

    public void Dispose()
    {
        _versionManager.Delete(_version);
    }

    public void Complete()
    {
        _versionManager.ReconcilePush(_version);
    }
}

我希望到目前为止我编写的所有代码都没有在版本中出现任何概念的能力.我只想包含一个简单的

I want the ability for all the code I've written thus far to not have any concept of being in a version. I just want to include a simple

Version = GetCurrentVersionWithinScope()

位于代码的最低级别.

在内存中同时运行多个实例的情况下,实现这样的方法最安全的方法是什么,而使用错误版本的风险很小.

What is the safest way of implementing something like this with little risk of using the wrong version if there are multiple instances in memory simultaneously running.

我非常幼稚的方法是找到进程正在运行的内存块的唯一标识符.然后将当前的工作版本存储到全局数组或并发字典中.然后在需要当前版本的代码中,使用其内存标识符块,并将其映射到创建的版本.

My very naive approach would be find if there is a unique identifier for a block of memory a process is running in. Then store the current working version to a global array or concurrent dictionary. Then in the code where I need the current version, I use its block of memory identifier and it maps to the version that was created.

用法示例:

using (var scope = new VersionScope(_config))
{
    AddFeature(); // This has no concept of scope passed to it, and could error out forcing a dispose() without a complete()
    scope.Complete();
}

推荐答案

最直接的方法是使用ThreadStaticThreadLocal将当前版本存储在线程本地存储中.这样,多个线程将不会互相干扰.例如,假设我们对类进行版本化:

The most straightforward approach would be to use ThreadStatic or ThreadLocal to store current version in thread local storage. That way multiple threads will not interfere with each other. For example suppose we version class:

public class Version {
    public Version(int number) {
        Number = number;
    }
    public int Number { get; }

    public override string ToString() {
        return "Version " + Number;
    }
}

然后VersionScope的实现可以像这样:

Then implementation of VersionScope can go like this:

public sealed class VersionScope : IDisposable {
    private bool _isCompleted;
    private bool _isDisposed;
    // note ThreadStatic attribute
    [ThreadStatic] private static Version _currentVersion;
    public static Version CurrentVersion => _currentVersion;

    public VersionScope(int version) {
        _currentVersion = new Version(version);
    }

    public void Dispose() {
        if (_isCompleted || _isDisposed)
            return;
        var v = _currentVersion;
        if (v != null) {
            DeleteVersion(v);
        }
        _currentVersion = null;
        _isDisposed = true;
    }

    public void Complete() {
        if (_isCompleted || _isDisposed)
            return;
        var v = _currentVersion;
        if (v != null) {
            PushVersion(v);
        }
        _currentVersion = null;
        _isCompleted = true;
    }

    private void DeleteVersion(Version version) {
        Console.WriteLine($"Version {version} deleted");
    }

    private void PushVersion(Version version) {
        Console.WriteLine($"Version {version} pushed");
    }
}

它可以工作,但是不支持嵌套作用域,这不好,因此,要修复此问题,我们需要在启动新作用域时存储先前的作用域,然后将其还原到CompleteDispose上:

It will work, but it will not support nested scopes, which is not good, so to fix we need to store previous scope when starting new one, and restore it on Complete or Dispose:

public sealed class VersionScope : IDisposable {
    private bool _isCompleted;
    private bool _isDisposed;
    private static readonly ThreadLocal<VersionChain> _versions = new ThreadLocal<VersionChain>();

    public static Version CurrentVersion => _versions.Value?.Current;

    public VersionScope(int version) {
        var cur = _versions.Value;
        // remember previous versions if any
        _versions.Value = new VersionChain(new Version(version), cur);
    }

    public void Dispose() {
        if (_isCompleted || _isDisposed)
            return;
        var cur = _versions.Value;
        if (cur != null) {
            DeleteVersion(cur.Current);
            // restore previous
            _versions.Value = cur.Previous;
        }
        _isDisposed = true;
    }

    public void Complete() {
        if (_isCompleted || _isDisposed)
            return;
        var cur = _versions.Value;
        if (cur != null) {
            PushVersion(cur.Current);
            // restore previous
            _versions.Value = cur.Previous;
        }
        _isCompleted = true;
    }

    private void DeleteVersion(Version version) {
        Console.WriteLine($"Version {version} deleted");
    }

    private void PushVersion(Version version) {
        Console.WriteLine($"Version {version} pushed");
    }

    // just a class to store previous versions
    private class VersionChain {
        public VersionChain(Version current, VersionChain previous) {
            Current = current;
            Previous = previous;
        }

        public Version Current { get; }
        public VersionChain Previous { get; }
    }
}

已经可以使用它了.用法示例(我使用单个线程,但是如果有多个线程分别执行此操作-它们不会互相干扰):

That's already something you can work with. Sample usage (I use single thread, but if there were multiple threads doing this separately - they will not interfere with each other):

static void Main(string[] args) {
    PrintCurrentVersion(); // no version
    using (var s1 = new VersionScope(1)) {
        PrintCurrentVersion(); // version 1
        s1.Complete();
        PrintCurrentVersion(); // no version, 1 is already completed
        using (var s2 = new VersionScope(2)) {
            using (var s3 = new VersionScope(3)) {
                PrintCurrentVersion(); // version 3
            } // version 3 deleted
            PrintCurrentVersion(); // back to version 2
            s2.Complete();
        }
        PrintCurrentVersion(); // no version, all completed or deleted
    }
    Console.ReadKey();
}

private static void PrintCurrentVersion() {
    Console.WriteLine("Current version: " + VersionScope.CurrentVersion);
}

但是,当您使用异步调用时,这将不起作用,因为ThreadLocal绑定到了一个线程,但是异步方法可以跨越多个线程.但是,有一个名为AsyncLocal的类似构造,该值将通过异步调用流动.因此,我们可以向VersionScope添加构造函数参数,以指示是否需要异步流.事务作用域以类似的方式工作-您将TransactionScopeAsyncFlowOption传递给TransactionScope构造函数,以指示它是否将流经异步调用.

This however will not work when you are using async calls, because ThreadLocal is tied to a thread, but async method can span multiple threads. However, there is similar construct named AsyncLocal, which value will flow through asynchronous calls. So we can add constructor parameter to VersionScope indicating if we need async flow or not. Transaction scope works in a similar way - there is TransactionScopeAsyncFlowOption you pass into TransactionScope constructor indicating if it will flow through async calls.

修改后的版本如下:

public sealed class VersionScope : IDisposable {
    private bool _isCompleted;
    private bool _isDisposed;
    private readonly bool _asyncFlow;
    // thread local versions
    private static readonly ThreadLocal<VersionChain> _tlVersions = new ThreadLocal<VersionChain>();
    // async local versions
    private static readonly AsyncLocal<VersionChain> _alVersions = new AsyncLocal<VersionChain>();
    // to get current version, first check async local storage, then thread local
    public static Version CurrentVersion => _alVersions.Value?.Current ?? _tlVersions.Value?.Current;
    // helper method
    private VersionChain CurrentVersionChain => _asyncFlow ? _alVersions.Value : _tlVersions.Value;

    public VersionScope(int version, bool asyncFlow = false) {
        _asyncFlow = asyncFlow;

        var cur = CurrentVersionChain;
        // remember previous versions if any
        if (asyncFlow) {
            _alVersions.Value = new VersionChain(new Version(version), cur);
        }
        else {
            _tlVersions.Value = new VersionChain(new Version(version), cur);
        }
    }

    public void Dispose() {
        if (_isCompleted || _isDisposed)
            return;
        var cur = CurrentVersionChain;
        if (cur != null) {
            DeleteVersion(cur.Current);
            // restore previous
            if (_asyncFlow) {
                _alVersions.Value = cur.Previous;
            }
            else {
                _tlVersions.Value = cur.Previous;
            }
        }
        _isDisposed = true;
    }

    public void Complete() {
        if (_isCompleted || _isDisposed)
            return;
        var cur = CurrentVersionChain;
        if (cur != null) {
            PushVersion(cur.Current);
            // restore previous
            if (_asyncFlow) {
                _alVersions.Value = cur.Previous;
            }
            else {
                _tlVersions.Value = cur.Previous;
            }
        }
        _isCompleted = true;
    }

    private void DeleteVersion(Version version) {
        Console.WriteLine($"Version {version} deleted");
    }

    private void PushVersion(Version version) {
        Console.WriteLine($"Version {version} pushed");
    }

    // just a class to store previous versions
    private class VersionChain {
        public VersionChain(Version current, VersionChain previous) {
            Current = current;
            Previous = previous;
        }

        public Version Current { get; }
        public VersionChain Previous { get; }
    }
}

具有异步流的范围的示例用法:

Sample usage of scopes with async flow:

static void Main(string[] args) {
    Test();
    Console.ReadKey();
}

static async void Test() {
    PrintCurrentVersion(); // no version
    using (var s1 = new VersionScope(1, asyncFlow: true)) {
        await Task.Delay(100);
        PrintCurrentVersion(); // version 1
        await Task.Delay(100);
        s1.Complete();
        await Task.Delay(100);
        PrintCurrentVersion(); // no version, 1 is already completed
        using (var s2 = new VersionScope(2, asyncFlow: true)) {
            using (var s3 = new VersionScope(3, asyncFlow: true)) {
                PrintCurrentVersion(); // version 3
            } // version 3 deleted
            await Task.Delay(100);
            PrintCurrentVersion(); // back to version 2
            s2.Complete();
        }
        await Task.Delay(100);
        PrintCurrentVersion(); // no version, all completed or deleted
    }
}

private static void PrintCurrentVersion() {
    Console.WriteLine("Current version: " + VersionScope.CurrentVersion);
}

这篇关于交易范围类似的功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆