c# - excepciones - esperar en el bloque try-finally
try finally c# (1)
He estado jugando con Visual Studio 14 CTP 2. Esta versión de C # vNext permite el uso de la palabra clave await dentro de un bloque final.
Estoy tratando de averiguar cómo se implementó esto. Sé que esto es un detalle de implementación y está sujeto a cambios hasta la versión RTM, pero aún así tuve que elegir mi cerebro con esta característica.
Para probar y comprender el código generado por el compilador subyacente, creé este código de ejemplo:
private async void button1_Click(object sender, EventArgs e)
{
try
{
}
finally
{
await MyFinallyTest();
}
}
private async Task MyFinallyTest()
{
await Task.Delay(1000);
}
Esta es la clase generada por el compilador:
[CompilerGenerated]
private sealed class <button1_Click>d__1 : IAsyncStateMachine
{
public int <>1__state;
public Form1 <>4__this;
public object <>7__wrap1;
public int <>7__wrap2;
public AsyncVoidMethodBuilder <>t__builder;
public TaskAwaiter <>u__$awaiter0;
private void MoveNext()
{
int num = this.<>1__state;
try
{
TaskAwaiter awaiter;
switch (num)
{
case 1:
break;
default:
{
this.<>7__wrap1 = null;
this.<>7__wrap2 = 0;
try
{
}
catch (object obj2)
{
this.<>7__wrap1 = obj2;
}
awaiter = this.<>4__this.MyFinallyTest().GetAwaiter();
if (awaiter.IsCompleted)
{
goto Label_0096;
}
this.<>1__state = num = 1;
this.<>u__$awaiter0 = awaiter;
Form1.<button1_Click>d__1 stateMachine = this;
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Form1.<button1_Click>d__1>(ref awaiter, ref stateMachine);
return;
}
}
awaiter = this.<>u__$awaiter0;
this.<>u__$awaiter0 = new TaskAwaiter();
this.<>1__state = num = -1;
Label_0096:
awaiter.GetResult();
awaiter = new TaskAwaiter();
object obj3 = this.<>7__wrap1;
if (obj3 != null)
{
Exception source = obj3 as Exception;
if (source <= null)
{
throw obj3;
}
ExceptionDispatchInfo.Capture(source).Throw();
}
int num1 = this.<>7__wrap2;
this.<>7__wrap1 = null;
}
catch (Exception exception2)
{
this.<>1__state = -2;
this.<>t__builder.SetException(exception2);
return;
}
this.<>1__state = -2;
this.<>t__builder.SetResult();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
}
Por lo que entiendo, el compilador toma el bloque de código finally
y lo mueve después del bloque de catch
generado por el compilador. Algo similar a lo que teníamos que hacer si queríamos await
algo en un catch-end hasta C # 6.0.
Veo un par de cosas que no entiendo:
El compilador está agregando un bloque
catch
generado (que no estaba presente en mi método) en forma decatch (object obj2)
y configurando suobject
interno en la excepción obj2. No entiendo por qué se está haciendo esto.El bloque final que creé ya no existe. ¿Significa eso que cualquier código que se
awaited
dentro de un bloquefinally
no puede "disfrutar" de las garantías que obtenemos al poner realmente el código dentro de ese bloque?
El compilador está girando:
try
{
Foo();
}
finally
{
Bar();
}
en algo como:
Exception caught = null;
try
{
Foo();
}
catch (Exception e)
{
caught = e;
}
Bar();
if (caught != null)
{
throw caught;
}
... pero de forma asíncrona. Termina con el mismo resultado: su bloque finally
todavía se ejecutará, ya sea que se lance o no una excepción, simplemente está usando "capturar todo y luego ejecutarse" en lugar de la versión IL de finally
.
Le sugiero que considere cómo se vería el flujo de ejecución en diversas situaciones (por ejemplo, si se lanza una excepción en el bloque try) y se convence de que el resultado será el esperado en cada caso.
En términos de por qué esto no estaba en C # 5, Mads Torgersen escribe en un documento CTP de C # 6:
En C # 5.0 no permitimos que la palabra clave
await
encatch
yfinally
bloquee, porque de alguna manera nos convencimos a nosotros mismos de que no era posible implementarlo. Ahora lo hemos descubierto, así que aparentemente no era imposible después de todo.