Hace poco tiempo me encontré con la necesidad de un mecanismo seguro de "apagar y olvidar" para ejecutar el código de forma asincrónica.Tipo seguro de invocación de delegado asíncrono en C#
Idealmente, lo que yo quiero hacer es algo como:
var myAction = (Action)(() => Console.WriteLine("yada yada"));
myAction.FireAndForget(); // async invocation
Por desgracia, la elección obvia de llamar BeginInvoke()
sin la correspondiente EndInvoke()
no funciona - el resultado es una pérdida de recursos lenta (ya que la el estado de ejecución es retenido por el tiempo de ejecución y nunca se lanzó ... espera una llamada eventual al EndInvoke()
. Tampoco puedo ejecutar el código en el grupo de subprocesos de .NET porque puede llevar mucho tiempo completarlo (se recomienda solo ejecuta un código relativamente efímero en el grupo de subprocesos): esto hace que sea imposible usar el ThreadPool.QueueUserWorkItem()
.
Inicialmente, solo necesitaba este comportamiento para los métodos cuya firma coincide con Action
, Action<...>
o Func<...>
. Así que armé un conjunto de métodos de extensión (consulte la lista a continuación) que me permiten hacer esto sin tener que encontrar la fuga de recursos. Hay sobrecargas para cada versión de Action/Func.
Lamentablemente, ahora deseo transferir este código a .NET 4, donde el número de parámetros genéricos en Action y Func se ha incrementado sustancialmente. Antes de escribir un script T4 para generar estos, también esperaba encontrar una manera más simple y más elegante para hacer esto. Cualquier idea es bienvenida.
public static class AsyncExt
{
public static void FireAndForget(this Action action)
{
action.BeginInvoke(OnActionCompleted, action);
}
public static void FireAndForget<T1>(this Action<T1> action, T1 arg1)
{
action.BeginInvoke(arg1, OnActionCompleted<T1>, action);
}
public static void FireAndForget<T1,T2>(this Action<T1,T2> action, T1 arg1, T2 arg2)
{
action.BeginInvoke(arg1, arg2, OnActionCompleted<T1, T2>, action);
}
public static void FireAndForget<TResult>(this Func<TResult> func, TResult arg1)
{
func.BeginInvoke(OnFuncCompleted<TResult>, func);
}
public static void FireAndForget<T1,TResult>(this Func<T1, TResult> action, T1 arg1)
{
action.BeginInvoke(arg1, OnFuncCompleted<T1,TResult>, action);
}
// more overloads of FireAndForget<..>() for Action<..> and Func<..>
private static void OnActionCompleted(IAsyncResult result)
{
var action = (Action)result.AsyncState;
action.EndInvoke(result);
}
private static void OnActionCompleted<T1>(IAsyncResult result)
{
var action = (Action<T1>)result.AsyncState;
action.EndInvoke(result);
}
private static void OnActionCompleted<T1,T2>(IAsyncResult result)
{
var action = (Action<T1,T2>)result.AsyncState;
action.EndInvoke(result);
}
private static void OnFuncCompleted<TResult>(IAsyncResult result)
{
var func = (Func<TResult>)result.AsyncState;
func.EndInvoke(result);
}
private static void OnFuncCompleted<T1,TResult>(IAsyncResult result)
{
var func = (Func<T1, TResult>)result.AsyncState;
func.EndInvoke(result);
}
// more overloads of OnActionCompleted<> and OnFuncCompleted<>
}
pregunta fresca. Código doloroso! Lo único que tienen en común es que todos ellos son MulticastDelegate. No estoy seguro de que vaya a haber una forma segura de hacer dieta con el código. ¿Cómo genera MS todo en primer lugar? – spender
@spender: el compilador genera automáticamente los métodos 'BeginInvoke' /' EndInvoke' en cada tipo de delegado fuertemente tipado a los argumentos del delegado. – LBushkin
Seguro. Me parece que fomenta un código repetitivo considerable, y me sorprende que .net4 no haya introducido una forma más genérica (o supergenérica) de declarar este tipo de construcciones ... aunque estaría de acuerdo con eso en la mayoría de los casos. código, las listas de parámetros no excederían de 5 o más de longitud. – spender