为什么在处理后需要进行交易处理?

10

我正在尝试使用一些环境事务范围(感谢),这是我以前没有做过的,我看到了一些奇怪的行为,我正在努力理解。

我正在尝试加入当前事务范围并在其成功完成后进行一些操作。我的加入参与者由于持有某些资源而实现IDisposable。我有一个简单的示例展示了奇怪的行为。

对于这个类,

class WtfTransactionScope : IDisposable, IEnlistmentNotification
{
    public WtfTransactionScope()
    {
        if(Transaction.Current == null)
            return;
        Transaction.Current.EnlistVolatile(this, EnlistmentOptions.None);
    }
    void IEnlistmentNotification.Commit(Enlistment enlistment)
    {
        enlistment.Done();
        Console.WriteLine("Committed");
    }

    void IEnlistmentNotification.InDoubt(Enlistment enlistment)
    {
        enlistment.Done();
        Console.WriteLine("InDoubt");
    }

    void IEnlistmentNotification.Prepare(
        PreparingEnlistment preparingEnlistment)
    {
        Console.WriteLine("Prepare called");
        preparingEnlistment.Prepared();
        Console.WriteLine("Prepare completed");
    }

    void IEnlistmentNotification.Rollback(Enlistment enlistment)
    {
        enlistment.Done();
        Console.WriteLine("Rolled back");
    }

    public void Dispose()
    {
        Console.WriteLine("Disposed");
    }
}

当按照这里展示的使用时

using(var scope = new TransactionScope())
using(new WtfTransactionScope())
{
    scope.Complete();
}

控制台输出表明出现了意外情况:

已释放
已调用 Prepare
已提交
Prepare 已完成

啥?

我在事务完成之前被释放了。这......有点否定了与事务范围一起工作的好处。我希望一旦事务成功(或失败),我就会收到通知,以便我可以进行一些操作。不幸的是,我假设这将发生在 scope.Complete() 之后,在我们移出 using 范围之前被释放。显然,这并非如此。

当然,我可以通过“黑科技”解决这个问题。但是我还有其他问题,这些问题本质上阻止我这样做。在这种情况下,我必须使用其他方法来解决。

我在这里做错了什么吗?还是这是预期行为?可以采取不同的方法来防止这种情况发生吗??


你能否从WtfTransactionScope类中删除Dispose方法,改为在作用域完成时自动清理?这使它更像是一个“fire and forget”的登记,并且只要您可以保证最终会从作用域得到一些通知,就应该起作用。 - Dan Bryant
顺便提一下,我怀疑这是有意设计的;Complete 可能标记为完成,但直到退出作用域才真正完成交易。 - Dan Bryant
@DanBryant:问题在于,如果没有环境范围,我最终需要清理。无法摆脱它。 - user1228
如果没有环境范围,你不可以在构造函数期间什么都不做,以免留下需要清理的东西吗? - Dan Bryant
@DanBryant:不详细解释了……我必须支持两种情况,没有其他选择。我发誓我没有说谎。我已经用C#编程十年了,不是新手错误。我只是想创造最快乐的路径和最广阔的成功之坑。希望这可以涵盖有环境事务和没有环境事务的情况。 - user1228
1个回答

14

这是一种自我造成的痛苦。您违反了IDisposable的一个非常基本的规则,即对象只有在没有任何其他使用时才应被处理。当您的using语句调用Dispose()时,该对象正在使用中,因为您在WtfTransactionScope构造函数中传递了对该对象的引用。在它完成后,您不能处理它,这必然意味着您必须在using语句完成TransactionScope并提交/回滚事务之后才能将其处理。

至于让它变得美观,我会留给您自行思考,但一个显而易见的方法是:

using(var wtf = new WtfTransactionScope())
using(var scope = new TransactionScope())
{
    wtf.Initialize();
    scope.Complete();
}

Prepare completed只是一个没有帮助的调试语句。直接删除它即可。


2
另外,注意Complete方法并不提交事务,它只是标记范围为已完成。实际上,事务可能只有在另一个外部范围完成且被释放时才会提交。(今天的投票次数用完了。) - usr
嗯,我考虑过这个问题,不过这会带来另一个问题...当我实例化Wtf时,该作用域并不存在,所以我不能在其中注册。那么,Wtf如何知道,在其生命周期和作用域内,出现了一个新的事务,以便它可以加入呢? - user1228
这就是你的Initialize()方法所做的事情 :) - Hans Passant
@HansPassant:唉,我的成功之坑就这样没了。 - user1228

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接