ejemplos - ejecutar dos metodos al mismo tiempo en c#
c#.net ¿por qué parece que Task.Run maneja Func<T> de manera diferente a otro código? (6)
El nuevo método estático Task.Run que forma parte de .NET 4.5 no parece comportarse como se podría esperar.
Por ejemplo:
Task<Int32> t = Task.Run(()=>5);
compila bien, pero
Task<Int32> t = Task.Run(MyIntReturningMethod);
...
public Int32 MyIntReturningMethod() {
return (5);
}
se queja de que MyIntReturningMethod está devolviendo el tipo incorrecto.
Quizás no entiendo a qué sobrecarga de Task.Run se está llamando. Pero en mi mente, mi código lambda anterior se parece mucho a un Func<Int32>
, y MyIntReturningMethod es definitivamente compatible con Func<Int32>
¿Alguna idea de lo que está pasando? Miguel
Se suponía que esto se solucionaba en .Net 4.0, pero Task.Run () es nuevo en .Net 4.5
.NET 4.5 tiene su propia ambigüedad de sobrecarga al agregar el Task.Run(Func<Task<T>>)
. Y el soporte para async / await en C # versión 5. Lo que permite una conversión implícita de T foo()
a Func<Task<T>>
.
Eso es azúcar de sintaxis que es bastante dulce para asíncrono / espera pero produce caries aquí. La omisión de la palabra clave async
en la declaración del método no se considera en la selección de sobrecarga del método, que abre otra caja de miseria pandora con programadores que olvidan usar async cuando quisieron hacerlo. De lo contrario, sigue la convención habitual de C # de que solo el nombre del método y los argumentos en la firma del método se consideran para la selección de la sobrecarga del método.
Se requiere el uso explícito del tipo de delegado para resolver la ambigüedad.
(Por supuesto, para salir del problema, simplemente diga Task.Run((Func<int>)MyIntReturningMethod)
.)
Esto no tiene absolutamente nada que ver con la Task
y así sucesivamente.
Un problema que debe tener en cuenta es que cuando hay muchas sobrecargas, el texto de error del compilador se centrará en un solo "par" de sobrecargas. Así que eso es confuso. La razón es que el algoritmo para determinar la mejor sobrecarga considera todas las sobrecargas, y cuando ese algoritmo concluye que no se puede encontrar la mejor sobrecarga, eso no produce un determinado par de sobrecargas para el texto de error porque todas las sobrecargas pueden (o no) han estado involucrados
Para entender lo que sucede, vea en su lugar esta versión simplificada:
static class Program
{
static void Main()
{
Run(() => 5); // compiles, goes to generic overload
Run(M); // won''t compile!
}
static void Run(Action a)
{
}
static void Run<T>(Func<T> f)
{
}
static int M()
{
return 5;
}
}
Como vemos, esto no tiene absolutamente ninguna referencia a la Task
, pero aún produce el mismo problema.
Tenga en cuenta que las conversiones de funciones anónimas y las conversiones de grupos de métodos (aún) no son exactamente lo mismo. Los detalles se encuentran en la Especificación del lenguaje C # .
La lambda
() => 5
En realidad, ni siquiera es convertible al tipo System.Action
. Si intentas hacer:
Action myLittleVariable = () => 5;
se producirá un error con el error CS0201: solo se pueden usar como una declaración la asignación, la llamada, el incremento, el decremento, la espera y nuevas expresiones de objetos . Así que está realmente claro qué sobrecarga utilizar con la lambda.
Por otro lado, el grupo de métodos:
M
es convertible tanto a Func<int>
como a Action
. Recuerde que está perfectamente permitido no recoger un valor de retorno, al igual que la declaración:
M(); // don''t use return value
Es válido por sí mismo.
Este tipo de respuesta responde a la pregunta, pero daré un ejemplo adicional para hacer un punto adicional. Considera el ejemplo:
static class Program
{
static void Main()
{
Run(() => int.Parse("5")); // compiles!
}
static void Run(Action a)
{
}
static void Run<T>(Func<T> f)
{
}
}
En este último ejemplo, ¡la lambda es realmente convertible a ambos tipos de delegados! (Solo intente eliminar la sobrecarga genérica). Para el lado derecho de la flecha lambda =>
es una expresión:
int.Parse("5")
que es válido como una declaración por sí mismo. Pero la resolución de sobrecarga todavía puede encontrar una mejor sobrecarga en este caso. Como dije antes, verifique la especificación de C #.
Inspirado por HansPassant y BlueRaja-DannyPflughoeft, aquí hay un ejemplo final (creo):
class Program
{
static void Main()
{
Run(M); // won''t compile!
}
static void Run(Func<int> f)
{
}
static void Run(Func<FileStream> f)
{
}
static int M()
{
return 5;
}
}
Tenga en cuenta que, en este caso, no hay absolutamente ninguna manera de que el int
5
pueda convertir en un System.IO.FileStream
. Aún así la conversión del grupo de métodos falla. Esto podría estar relacionado con el hecho de que con dos métodos ordinarios int f();
y FileStream f();
, por ejemplo, heredado por alguna interfaz de dos interfaces base diferentes, no hay manera de resolver la llamada f();
. El tipo de retorno no es parte de la firma de un método en C #.
Todavía evito introducir Task
en mi respuesta, ya que podría dar una impresión errónea de qué trata este problema. La gente tiene dificultades para entender la Task
, y es relativamente nueva en el BCL.
Esta respuesta ha evolucionado mucho. Al final, resulta que este es realmente el mismo problema subyacente que en el hilo ¿Por qué Func<T>
ambiguo con Func<IEnumerable<T>>
? . Mi ejemplo con Func<int>
y Func<FileStream>
es casi tan claro. Eric Lippert da una buena respuesta en ese otro hilo.
Aquí está mi puñalada:
public class MyTest
{
public void RunTest()
{
Task<Int32> t = Task.Run<Int32>(new Func<int>(MyIntReturningMethod));
t.Wait();
Console.WriteLine(t.Result);
}
public int MyIntReturningMethod()
{
return (5);
}
}
Cuando pasa un Func<TResult>
a un método Run<TResult>(Func<TResult>)
no tiene que especificar el genérico en la llamada al método porque puede inferirlo. Tu lambda hace esa inferencia.
Sin embargo, su función no es realmente una función Func<TResult>
mientras que la lambda era.
Si lo hace Func<Int32> f = MyIntReturningMethod
, funciona. Ahora, si especifica Task.Run<Int32>(MyIntReturningMethod)
también esperaría que funcione. Sin embargo, no puede decidir si debe resolver la sobrecarga Func<Task<TResult>>
o la sobrecarga Func<TResult>
, y eso no tiene mucho sentido porque es obvio que el método no está devolviendo una tarea.
Si compilas algo simple como sigue:
void Main()
{
Thing(MyIntReturningMethod);
}
public void Thing<T>(Func<T> o)
{
o();
}
public Int32 MyIntReturningMethod()
{
return (5);
}
La IL se ve así ...
IL_0001: ldarg.0
IL_0002: ldarg.0
IL_0003: ldftn UserQuery.MyIntReturningMethod
IL_0009: newobj System.Func<System.Int32>..ctor
IL_000E: call UserQuery.Thing
(Algunas de las cosas adicionales son de las adiciones de LINQ Pad ... como la parte UserQuery)
La IL se ve idéntica como si hicieras un lanzamiento explícito. Así que parece que el compilador no sabe realmente qué método usar. Así que no sabe qué reparto crear automáticamente.
Puede usar Task.Run<Int32>((Func<Int32>)MyIntReturningMethod)
para ayudarlo un poco. Aunque estoy de acuerdo en que esto parece algo que el compilador debería poder manejar. Debido a que Func<Task<Int32>>
no es lo mismo que Func<Int32>
, entonces no tiene sentido que confundan al compilador.
El enfoque de Tyler Jensen funciona para mí.
Además, puedes intentar esto usando una expresión lambda:
public class MyTest
{
public void RunTest()
{
Task<Int32> t = Task.Run<Int32>(() => MyIntReturningMethod());
t.Wait();
Console.WriteLine(t.Result);
}
public int MyIntReturningMethod()
{
return (5);
}
}
Parece un problema de resolución de sobrecarga. El compilador no puede saber a qué sobrecarga está llamando (porque primero tiene que encontrar el delegado correcto para crear, que no sabe porque eso depende de la sobrecarga que está llamando). Tendría que adivinar y verificar, pero supongo que no es tan inteligente.