Using Using Automagically Disposes and Rolls Back

I talked about

scoping and disposing of transactions
last
week, and received prompt and interesting comments from two competent and experienced Revit add-in developers, Danny Polkinhorn and Guy Robinson (your name has to end in ‘y’ to be really good – tongue in cheek).

Danny says: Should we also use Try…Catch blocks as well?


  using( Transaction trans
    = new Transaction( doc ) )
  {
    try
    {
      trans.Start();
      //code here
      trans.Commit();
    }
    catch( Exception ex )
    {
      trans.RollBack();
      //exception code here
    }
  }

Do we need to roll back the transaction or does the Using block take care of that?

Guy replies: I’m with Danny here, prefer to gracefully fail so use this format:


  TransactionGroup group;
  try
  {
    using( group = new TransactionGroup( doc ) )
    {
      group.Start( "Some group" );
      // . . .
    }
  }
  catch( Exception )
  {
    if( group.HasStarted() && !group.HasEnded() )
    {
      group.RollBack();
    }
  }

As so often in the past, I leave the final verdict to Arnošt Löbel, who replies with a clear ‘no’ to the suggestion of adding an exception handler around the ‘using’ block, because, as implied by the title, it automatically disposes, rolls back, and even handles exceptions:

Answer: No, there is no need to explicitly roll back a transaction (or sub-transaction, transaction group) declared in a ‘using’ block. The destructor of the object will take of the rolling back when the object leaves the ‘using’ block.

However, it is still the responsibility of the programmer to commit the object properly.
For example, using what Guy wrote:


  using( TransactionGroup group
    = new TransactionGroup( doc ) )
  {
    group.Start( "Some group" );
 
    // do something here
 
    // need to commit explicitly to
    // avoid implicit rolling back
 
    group.Commit(); 
  }

By the way: I always commit or rollback explicitly if it is under my control.
I only rely on the destructor for exceptions.
I believe it makes the code easier to comprehend.

The snippet above is equivalent to (but more elegant than) the following:


  TransactionGroup group
    = new TransactionGroup( doc );
 
  try
  {
    group.Start( "Some group" );
    // do something here
    group.Commit();
  }
  catch( Exception )
  {
    // this needs to be tested to 
    // avoid another potential exception
 
    if( group.HasStarted() )
    {
      group.RollBack();
    }
 
    // should re-throw if you do 
    // not know how to handle
 
    throw; 
  }

Adding a ‘using’ block around this try-catch (like Guy did) is completely unnecessary and worthless.

Again: If you always use ‘using’ for all scope object, you code will be more elegant, transparent and safer.


Comments

9 responses to “Using Using Automagically Disposes and Rolls Back”

  1. Ouch ;-) completely unnecessary and worthless.. true. That’ll teach me for copy/pasting from the initial example.
    using is certainly more elegant to write but Arnošt (unless I’m missing something) is not explaining how when using ‘using’ blocks we should be capturing exceptions? To me ‘using’ & try/catch/finally have very different purposes as ‘using’ is essentially try/finally. So to capture the exception you must wrap it in a try/catch.
    However, ‘using’ can be an advantage with TransactionGroups rather than standard transactions as commit order is not an issue for standard transactions.
    I also note he doesn’t test for !group.HasEnded() in the final snippet. The exception may have occurred after a safe commit though?
    Interesting discussion.
    Cheers,Guy

  2. In the original article Arnošt wrote:
    “”This is the safest way to guarantee that scoped objects are destroyed in the proper order “”
    Actually is major point in the original article? That we shouldn’t be relying on the CLR to dispose of the transactions or other scoped objects and we should be doing this if we want to capture the exceptions in our try/catch/finally blocks ?
    This discussion is well timed for those of us porting code to R13…

  3. Arnošt Löbel Avatar
    Arnošt Löbel

    Yes, that is exactly the point – CLR cannot guarantee that scope objects that are not scoped will be destroyed in time before Revit (all some other part of your application) starts complaining about the object not being destroyed yet. This can happen when calling an external command. Revit does not permit external command to leave transactions or transactions groups being left open after returning from the command. When it happens, the command is rendered as unsuccessful and all changes made in the command will be automatically rolled back. Since it is not exactly possible to schedule garbage disposal (well, it is possible, but it should not be done), relying on “using” block is the only way, besides explicit exception handling.
    This policy has not changed since (at least) R2010.
    Arnošt

  4. Arnošt Löbel Avatar
    Arnošt Löbel

    I am sorry, Guy, I did not mean any disrespect. Please understand there is a lot of communications sometimes between me and Jeremy before he publishes his article, and sometimes there are quotes left exactly how they were written in our internal email exchanges. In no way I meant you write worthless code. I respect your knowledge and work very much. That being said though, the fact remains that if there is try-catch around a using block in which an object is scoped and then later checked in the catch clause, that catch part will never be used. From that respect, the code is workless, because it had to be written (which takes time) but it never does anything during real execution, hence the effort was not worth of anything.
    Yes, I agree, try-catch has a different purpose other then what the “using” block does. I am not saying that programmers should only use “using” and never use exception handling. Of course a programmer needs exception catching in cases when some exceptions are sort of “expected” and the application can gracefully recover from them. However that was not the point I wanted to demonstrate. What I wanted was to make sure programmers understand that there are scope objects in the Revit API (such as transactions) which should be scoped in order to guarantee they are destroyed properly before returning back to Revit. If exception handling is not needed for no other reason, using the “using” block is a perfect and elegant way of achieving the objective.
    I do not understand your note about transaction and their issue with commit. I also do not understand your comment about group.HasEnded. If group has started, it cannot have been ended yet (this is not necessarily the case for transactions, but that is a different subject.)
    Arnošt

  5. Absolutely no disrespect taken ;-) You were quite right. I’ve also realised HasEnded shouldn’t be in catch but the finally.
    Cheers,
    Guy

  6. OK. So this is interesting. Normal .NET practice is to leave garbage disposal to the CLR. I didn’t realise this was the recommendation for Revit scoped classes. I’m going to experiment more but for now I think this is going to be my transaction(transactiongroup) template:
    var transaction = new Transaction(doc);
    try
    {
    transaction.Start(“new transaction”);
    //do something
    transaction.Commit();
    }
    catch (Exception ex)
    {
    if (transaction.HasStarted()) transaction.RollBack();
    }
    finally
    {
    if (transaction.HasEnded()) transaction.Dispose();
    }
    Very interesting discussion and appreciate the feedback Arnošt.
    Cheers,
    Guy

  7. Arnošt Löbel Avatar
    Arnošt Löbel

    Guy, your solution is all right. You are an experienced developer and you know your way around exception handling. If that is what you prefer to always use, it is perfectly fine. For the typical cases though, and for our general user, I do recommend the “using” block. It is simple enough, easy to adapt to, and it covers what needs to be covered. By the way, you do not have to dispose your transaction objects. That can be left for the GC. Revit only requires that you properly “end” transactions (and transaction groups, and sub transactions, and other scope-like objects) either by committing them or rolling them back. Yes, the “using” block also disposes the transaction, but that is actually not we are after. It is only because we know that the Dispose method will call the destructor which in turn will roll the transaction back if it is not ended yet – and that is what we want. In another words, we do not necessarily need the destructor, but we need what the destructor does and we like that it happens automatically upon leaving a “using” block. I hope I did not make my statement too complicated :-)

  8. This is an interesting discussion. For the general or beginner developer the fact ‘using’ hides thrown exceptions is a debugging and learning nightmare IMO. I guess we’ll agree to disagree.
    FWIW I did some tests and the try/catch/finally solution is about .3% slower than a ‘using’ with the advantage we can handle exceptions. My only other thought is if ITransactionFinalizer had an OnException method that would probably be ideal but not sure if this is possible internally. Will keep experimenting.
    Cheers,
    Guy

  9. Arnošt Löbel Avatar
    Arnošt Löbel

    But a ‘using’ block does not hide any exceptions at all; it looks like you misunderstood it. It only makes sure that the object scoped by the block is destroyed before leaving it. The exception, is any, is thrown after that in their original form. From that perspective a single ‘using’ block is better that the code in your example, because you ‘swallowed’ the exception without re-throwing it, which is often inappropriate and unsafe thing to do.
    Arnošt

Leave a Reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading