una que puede programacion poo interfaz interfaces instancia implementacion heredar declaracion declara cómo crear clase caracteristicas c# .net asynchronous task-parallel-library async-await

c# - puede - ¿Cómo implementar el método de interfaz que devuelve Tarea<T>?



que es una interfaz en programacion (4)

Tengo una interfaz

interface IFoo { Task<Bar> CreateBarAsync(); }

Hay dos métodos para crear Bar , uno asíncrono y uno sincrónico. Quiero proporcionar una implementación de interfaz para cada uno de estos dos métodos.

Para el método asincrónico, la implementación podría verse así:

class Foo1 : IFoo { async Task<Bar> CreateBarAsync() { return await AsynchronousBarCreatorAsync(); } }

Pero ¿CÓMO debería implementar la clase Foo2 que usa el método síncrono para crear Bar ?

Podría implementar el método para ejecutar sincrónicamente:

async Task<Bar> CreateBarAsync() { return SynchronousBarCreator(); }

El compilador advertirá entonces contra el uso de async en la firma del método:

Este método asíncrono carece de operadores ''en espera'' y se ejecutará de forma síncrona. Considere usar el operador ''aguardar'' para esperar las llamadas API sin bloqueo, o ''esperar a que Task.Run (...)'' realice un trabajo de CPU en un hilo de fondo.

O bien, podría implementar el método para devolver explícitamente Task<Bar> . En mi opinión, el código se verá menos legible:

Task<Bar> CreateBarAsync() { return Task.Run(() => SynchronousBarCreator()); }

Desde el punto de vista del rendimiento, supongo que ambos enfoques tienen la misma sobrecarga, o?

¿Qué enfoque debería elegir? ¿Implementar el método async sincrónicamente o explícitamente envolver la llamada al método síncrono en una Task ?

EDITAR

El proyecto en el que estoy trabajando es realmente un proyecto .NET 4 con extensiones async / await del paquete Microsoft Async NuGet. En .NET 4, Task.Run se puede reemplazar con TaskEx.Run . Conscientemente utilicé el método .NET 4.5 en el ejemplo anterior con la esperanza de aclarar la pregunta principal.


Cuando tiene que implementar un método asíncrono desde una interfaz y su implementación es sincrónica, puede usar la solución de Ned:

public Task<Bar> CreateBarAsync() { return Task.FromResult<Bar>(SynchronousBarCreator()); }

Con esta solución, el método parece asíncrono pero es sincrónico.

O la solución que propusiste:

Task<Bar> CreateBarAsync() { return Task.Run(() => SynchronousBarCreator()); }

De esta forma, el método es verdaderamente asincrónico.

No tiene una solución genérica que coincida con todos los casos de "Cómo implementar el método de interfaz que devuelve Tarea". Depende del contexto: ¿su implementación es lo suficientemente rápida como para invocarla en otro hilo es inútil? ¿Cómo se usa esta interfaz? ¿Cuándo se invoca este método (se congelará la aplicación)? ¿Es posible invocar su implementación en otro hilo?


Para complementar otras respuestas, hay una opción más, que creo que también funciona con .NET 4.0:

class Foo2 : IFoo { public Task<Bar> CreateBarAsync() { var task = new Task<Bar>(() => SynchronousBarCreator()); task.RunSynchronously(); return task; } }

Tenga en cuenta task.RunSynchronously() . Puede ser la opción más lenta, en comparación con Task<>.FromResult y TaskCompletionSource<>.SetResult , pero hay una diferencia sutil pero importante: el comportamiento de propagación de errores .

El enfoque anterior imitará el comportamiento del método async , donde nunca se lanza una excepción en el mismo marco de pila (desenrollado), sino que se almacena latente dentro del objeto Task . La persona que llama en realidad tiene que observarlo a través de una await task o await task task.Result . task.Result , en ese punto será relanzado.

Este no es el caso con Task<>.FromResult y TaskCompletionSource<>.SetResult , donde cualquier excepción lanzada por SynchronousBarCreator se propagará directamente a la persona que llama, desenrollando la pila de llamadas.

Tengo una explicación un poco más detallada de esto aquí:

Cualquier diferencia entre "esperar Task.Run (); return;" y "return Task.Run ()"?

En una nota lateral, sugiero agregar una disposición para la cancelación al diseñar interfaces (incluso si la cancelación no se usa / implementa actualmente):

interface IFoo { Task<Bar> CreateBarAsync(CancellationToken token); } class Foo2 : IFoo { public Task<Bar> CreateBarAsync(CancellationToken token) { var task = new Task<Bar>(() => SynchronousBarCreator(), token); task.RunSynchronously(); return task; } }


Prueba esto:

class Foo2 : IFoo { public Task<Bar> CreateBarAsync() { return Task.FromResult<Bar>(SynchronousBarCreator()); } }

Task.FromResult crea una tarea ya completada del tipo especificado utilizando el valor proporcionado.


Si está usando .NET 4.0, puede usar TaskCompletionSource<T> :

Task<Bar> CreateBarAsync() { var tcs = new TaskCompletionSource<Bar>(); tcs.SetResult(SynchronousBarCreator()); return tcs.Task }

En última instancia, si no hay nada asincrónico acerca de su método, debería considerar exponer un punto final síncrono ( CreateBar ) que crea una nueva Bar . De esa forma no hay sorpresas ni necesidad de envolver con una Task redundante.