简体   繁体   English

Delphi异常处理 - 如何正确清理?

[英]Delphi Exception Handling - How to clean up properly?

I'm looking at some code in an application of ours and came across something a little odd from what I normally do. 我正在查看我们的应用程序中的一些代码,并且从我通常做的事情中发现了一些奇怪的东西。 With exception handling and cleanup, we (as well as many other programmers out there, I'm sure) use a Try/Finally block embedded with a Try/Except block. 通过异常处理和清理,我们(以及其他许多程序员,我确定)使用嵌入了Try / Except块的Try / Finally块。 Now I'm used to the Try/Except inside the Try/Finally like so: 现在我已经习惯了Try / Finally中的Try / Except,如下所示:

Try
  Try
    CouldCauseError(X);
  Except
    HandleError;
  end;
Finally
  FreeAndNil(x);
end;

but this other block of code is reversed as so: 但是这个其他代码块是相反的:

Try
  Try
    CouldCauseError(X);
  Finally
    FreeAndNil(x);
  end;
Except
  HandleError;
end;

Looking around the web, I'm seeing folks doing this both ways, with no explanation as to why. 环顾网络,我看到人们两种方式都这样做,没有解释为什么。 My question is, does it matter which gets the outside block and which gets the inside block? 我的问题是,哪个获取外部块并且哪个获得内部块是否重要? Or will the except and finally sections get handled no matter which way it is structured? 或者,无论结构的哪种方式,都将处理除外和最后的部分? Thanks. 谢谢。

One difference is that try..finally..except is potentially vulnerable to an exception masking situation. 一个区别是try..finally..except可能容易受到异常屏蔽情况的影响。

Imagine that an exception occurs in CouldCauseError() . 想象一下,在CouldCauseError()中发生异常。 Then imagine that the attempt to FreeAndNIL (X) in the finally causes a further exception. 然后想象,企图FreeAndNIL(X)最终导致进一步的例外。 The original exception (quite possibly which lead to the instability leading to the FreeAndNIL () exception) is lost. 最初的异常(很可能导致导致FreeAndNIL ()异常的不稳定性)丢失了。 The except handler is now handling the "downstream" exception that occured after the original one. except处理程序现在处理在原始处理程序之后发生的“下游”异常。

try..except..finally avoids this of course and should be preferred for this reason (deal with exceptions as close as possible to their source). try..except ...当然可以避免这种情况,因此应该首选(处理尽可能接近其来源的例外情况)。

The other way to handle a simple case such as this (a single object being cleaned) is to include the cleanup both in the normal flow and in the exception handler: 处理诸如此类(正在清理的单个对象)的简单情况的另一种方法是在正常流程异常处理程序中包括清理:

try
  CouldCauseError(X);
  FreeAndNil(x);
except
  HandleError;
  FreeAndNil(x);
end;

This looks a little scary at first ("I need to be SURE that FreeAndNIL (X) is called, so I HAVE TO HAVE A FINALLY !!") but the only way that the first FreeAndNIL() might not be called is if there is an exception and if there is an exception you are FreeAndNIL()ing as well anyway, and it makes the order of cleanup in the event of an exception a little clearer (in the sense of removing noise that to some extent has to be "filtered" out in order to understand what is going on). 这看起来起初有点吓人(“我需要确保FreeAndNIL(X)被调用,所以我必须终于有一个!”),但第一FreeAndNIL()可能不会被调用的唯一途径是,如果有是一个例外, 如果你是FreeAndNIL()荷兰国际集团以及反正一个例外,它使清理的顺序一个异常清晰一点的情况下(在一定程度上必须是去除噪声的感觉“过滤“以了解正在发生的事情”。

But, I personally do not like it - if you change code in either the exception handler or the normal flow you risk breaking the cleanup behaviour, but depending on the code around such a block, and the size of the block itself, the reduction in "noise" can be argued to be justified in some cases, for the sake of simplification. 但是,我个人不喜欢它 - 如果您更改异常处理程序或正常流程中的代码,您可能会破坏清理行为,但取决于此类块周围的代码,以及块本身的大小,在某些情况下,为了简化,可以认为“噪声”是合理的。

However, this relies on the fact that FreeAndNIL () is actually " NILThenFree ()"... X is NIL'd before it is Free'd, so if an exception occurs in the FreeAndNIL (X) in the normal flow, then X will be NIL when the exception handler catches the exception raised by X.Free , so it will not attempt to "double-free" X. 但是,这依赖于以下事实: FreeAndNIL ()实际上是“ NILThenFree ()”... X在Free之前是NIL'd,所以如果在正常流程中FreeAndNIL (X)中发生异常,那么当异常处理程序捕获X.Free引发的异常时,X将为NIL,因此它不会尝试“双重释放”X.

Whatever you decide, I hope that helps. 无论你决定什么,我希望有所帮助。

The finally and except will both trigger, the order is up to you. 最终和将会触发,订单由您决定。 It depends on what you want to do in your finally or except block. 这取决于你在finally或except块中想要做什么。 Do you want to free something that is used in the except block? 你想要释放在except块中使用的东西吗? Place finally around the except block. 最后放在except块周围。

It all depends if the code in your finally block can raise an exception itself (then it needs to be protected by an upper level try except ), or if you need something in your exception handling that should be freed later (then it needs to be freed in an upper level finally block ). 这一切都取决于你的finally块中的代码本身是否会引发异常(然后它需要受到上层try保护除外 ),或者如果你需要在异常处理中需要稍后释放的东西(那么它需要是在上层解放最后阻止 )。

All that means that sometimes you can even have some code like: 这意味着有时你甚至可以拥有一些代码:

  try
    try
      try
        CouldCauseError(X);
      except
        HandleErrorWith(X);
      end;
    finally
      FreeAndNil(X); // and/or any resource cleanup
    end;
  except
    CatchAllError;
  end;

At first your code looks a little bit strange. 起初你的代码看起来有点奇怪。 I miss the creation of X. 我想念X的创作。

X := CreateAnX
try
  DoSomeThing(X);
finally
  FreeAndNil(x);
end;

It is important. 这很重要。 Because if you have code like this 因为如果你有这样的代码

// don't do something like this
try
  X := CreateAnX
  DoSomeThing(X);
finally
  FreeAndNil(x);
end;

you can be lucky and it works. 你可以幸运 ,它的工作原理。 But if the construction fails you can be " lucky " and get an access violation or you have bad luck and get an access violation some times later at an completely different code position. 但是,如果构造失败,您可以“ 幸运 ”并获得访问冲突,或者您运气不好并且稍后在完全不同的代码位置获得访问冲突。

An alternative could be 另一种选择可能是

X := nil;
try
  X := CreateAnX
  DoSomeThing(X);
finally
  FreeAndNil(x);
end;

Where to use except depends on what is your intention. 在哪里使用除了取决于你的意图。 When you want to catch every exception and know all calling code clean its problems (using try finally) then an outer except-block is the way to go 当你想捕获每个异常并知道所有调用代码清理它的问题(使用try finally)然后一个外部的except-block是要走的路

try
  X := CreateAnX
  try
    DoSomeThing(X);
  finally
    FreeAndNil(x);
  end;
except
  on e: Exception do
    LogException(e)
end;      

But always think about it when you want catch all errors. 但是当你想要捕获所有错误时,总要考虑它。 As an example (and I often see it wrong) don't do it in an Indy OnExecute-handler this way. 作为一个例子(我经常看错了)不要这样在Indy OnExecute-handler中这样做。 There you must use something like this 你必须使用这样的东西

try
  X := CreateAnX
  try
    DoSomeThing(X);
  finally
    FreeAndNil(x);
  end;
except
  on EIdException do
    raise;
  on e: Exception do
    LogException(e)
end;      

If you expect an exception because you throw it or (as an example) a conversation can fail, look for the most inner position to catch the error: 如果您因为抛出异常或(例如)对话可能失败而期望异常,请查找最内部位置以捕获错误:

X := CreateAnX
try
  DoSomeThing(X);
  try
    i := StrToInt(X.Text);
  except
    on EConvertError do
      i := 0;
  end;       
finally
  FreeAndNil(x);
end;

don't do it this way 不要这样做

X := CreateAnX
try
  try
    DoSomeThing(X);
    i := StrToInt(X.Text);
  except
    on EConvertError do
      i := 0;
  end;       
finally
  FreeAndNil(x);
end;

Do this only if you expect an EConvertError in DoSomeThing too. 仅当您在DoSomeThing中期望出现EConvertError时才执行此操作。 If DoSomeThing throws an EConvertError and you don't expect it, your code has a serious problem which need to be corrected. 如果DoSomeThing抛出EConvertError并且您没有预料到它,则您的代码存在严重问题,需要更正。 In this situation ensure that the user can save his work (perhaps as a copy, because his work could be damaged) and ensure you get the info about the problem. 在这种情况下,确保用户可以保存他的工作(可能作为副本,因为他的工作可能会被损坏)并确保您获得有关问题的信息。

At least try except is for handling Exceptions not for hiding them. 至少尝试除了处理异常而不是隐藏它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM