method - task c# example
Escribe tu propio método asíncrono (3)
Me gustaría saber cómo escribir sus propios métodos asíncronos de la manera "correcta".
He visto muchas publicaciones que explican el patrón async / await como este:
http://msdn.microsoft.com/en-us/library/hh191443.aspx
// Three things to note in the signature:
// - The method has an async modifier.
// - The return type is Task or Task<T>. (See "Return Types" section.)
// Here, it is Task<int> because the return statement returns an integer.
// - The method name ends in "Async."
async Task<int> AccessTheWebAsync()
{
// You need to add a reference to System.Net.Http to declare client.
HttpClient client = new HttpClient();
// GetStringAsync returns a Task<string>. That means that when you await the
// task you''ll get a string (urlContents).
Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
// You can do work here that doesn''t rely on the string from GetStringAsync.
DoIndependentWork();
// The await operator suspends AccessTheWebAsync.
// - AccessTheWebAsync can''t continue until getStringTask is complete.
// - Meanwhile, control returns to the caller of AccessTheWebAsync.
// - Control resumes here when getStringTask is complete.
// - The await operator then retrieves the string result from getStringTask.
string urlContents = await getStringTask;
// The return statement specifies an integer result.
// Any methods that are awaiting AccessTheWebAsync retrieve the length value.
return urlContents.Length;
}
private void DoIndependentWork()
{
resultsTextBox.Text += "Working......../r/n";
}
Esto funciona muy bien para cualquier método .NET que ya implemente esta funcionalidad como
- Operaciones de System.IO
- DataBase opertions
- Operaciones relacionadas con la red (descarga, carga ...)
Pero, ¿y si quiero escribir mi propio método que lleva bastante tiempo completar donde simplemente no hay Método que pueda usar y la gran carga está en el método DoIndependentWork
del ejemplo anterior?
En este método podría hacer:
- Manipulaciones de cadena
- Cálculos
- Manejando mis propios objetos
- Agregando, comparando, filtrando, agrupando, manejando cosas
- Listar operaciones, agregar, eliminar, enfrentar
Una vez más, me encontré con muchas publicaciones en las que la gente simplemente hace lo siguiente (tomando nuevamente el ejemplo anterior):
async Task<int> AccessTheWebAsync()
{
HttpClient client = new HttpClient();
Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
await DoIndependentWork();
string urlContents = await getStringTask;
return urlContents.Length;
}
private Task DoIndependentWork()
{
return Task.Run(() => {
//String manipulations
//Calculations
//Handling my own objects
//Aggregating, comparing, filtering, grouping, handling stuff
//List operations, adding, removing, coping
});
}
Puede observar que los cambios son que DoIndependentWork
ahora devuelve una Tarea y en la tarea AccessTheWebAsync
el método tiene una await
.
Las operaciones de carga pesada ahora están encapsuladas dentro de una Task.Run()
, ¿es esto todo lo que se necesita? Si eso es todo lo que se necesita es lo único que tengo que hacer para proporcionar el método asíncrono para cada método en mi biblioteca de la siguiente manera:
public class FooMagic
{
public void DoSomeMagic()
{
//Do some synchron magic...
}
public Task DoSomeMagicAsync()
{
//Do some async magic... ?!?
return Task.Run(() => { DoSomeMagic(); });
}
}
Sería bueno si pudieras explicarme, ya que incluso una pregunta muy votada como esta: ¿Cómo escribir el método asincrónico simple? solo lo explica con métodos ya existentes y solo usa el patrón asyn / await como este comentario de la pregunta mencionada lo lleva al punto: ¿Cómo escribir el método asincrónico simple?
Sería bueno si pudieras explicarme: ¿Cómo escribir el método asincrónico simple?
Primero, necesitamos entender qué significa un método async
. Cuando uno expone un método async
al usuario final que consume el método async
, le dice: "Escuche, este método le devolverá rápidamente con la promesa de completarlo en el futuro cercano". Eso es lo que está garantizando a sus usuarios.
Ahora, necesitamos entender cómo la Task
hace posible esta "promesa". Cuando preguntas en tu pregunta, ¿por qué simplemente agregar una Task.Run
dentro de mi método hace que sea válido esperar con la palabra clave Task.Run
?
Una Task
implementa el patrón GetAwaiter
, lo que significa que devuelve un objeto llamado awaiter
(en realidad se llama TaskAwaiter
). El objeto TaskAwaiter
implementa las interfaces INotifyCompletion
o ICriticalNotifyCompletion
, lo que expone un método OnCompleted
.
Todos estos objetos son utilizados por el compilador una vez que se usa la palabra clave await
. El compilador se asegurará de que, en el momento del diseño, su objeto implemente GetAwaiter
y, a su vez, lo use para compilar el código en una máquina de estados, lo que permitirá que su programa devuelva el control a la persona que llamó una vez que se reanude. terminado.
Ahora, hay algunas pautas a seguir. Un verdadero método asíncrono no utiliza subprocesos detrás de las escenas para hacer su trabajo (Stephan Cleary explica esto maravillosamente en There Is No Thread ), lo que significa que exponer un método que utiliza Task.Run
inside es un poco engañoso para los consumidores de su api, porque no asumirán ningún hilo adicional involucrado en su tarea. Lo que debe hacer es exponer su API de forma síncrona y dejar que el usuario la descargue mediante Task.Run
, controlando el flujo de ejecución.
async
métodos async
se usan principalmente para operaciones de E / S enlazadas , ya que naturalmente no necesitan ningún subproceso para consumirse mientras se está ejecutando la operación de E / S, y es por eso que los vemos mucho en las clases responsables de realizar operaciones de E / S, como conducir llamadas, llamadas de red, etc.
Sugiero leer el artículo Parallel PFX teams ¿Debo exponer los empaquetadores asíncronos para los métodos sincrónicos? que habla exactamente sobre lo que intentas hacer y por qué no es recomendable.
Respuesta real
Lo haces utilizando TaskCompletionSource
, que tiene una tarea Promesa que no ejecuta ningún código y solo:
"Representa el lado del productor de una Tarea desatada a un delegado, proporcionando acceso al lado del consumidor a través de la propiedad Tarea".
Devuelve esa tarea a la persona que llama cuando inicia la operación asincrónica y establece el resultado (o la excepción / cancelación) cuando finaliza. Asegurarte de que la operación es realmente asincrónica está contigo.
Aquí hay un buen ejemplo de este tipo de raíz de todos los métodos asíncronos en la implementación AsyncManualResetEvent
Stephen Toub :
class AsyncManualResetEvent
{
private volatile TaskCompletionSource<bool> _tcs = new TaskCompletionSource<bool>();
public Task WaitAsync() { return _tcs.Task; }
public void Set() { _tcs.TrySetResult(true); }
public void Reset()
{
while (true)
{
var tcs = _tcs;
if (!tcs.Task.IsCompleted ||
Interlocked.CompareExchange(ref _tcs, new TaskCompletionSource<bool>(), tcs) == tcs)
return;
}
}
}
Fondo
Básicamente hay dos razones para usar async-await
:
- Escalabilidad mejorada : cuando tiene un trabajo intensivo de
I/O
(u otras operaciones inherentemente asíncronas), puede llamarlo de forma asíncrona y, por lo tanto, libera el hilo de llamada y puede realizar otro trabajo mientras tanto. - Descarga: cuando tienes un trabajo intensivo de
CPU
, puedes llamarlo de forma asíncrona, lo que mueve el trabajo de un hilo a otro (principalmente utilizado para hilos de laGUI
).
Entonces la mayoría de los. Las llamadas asincrónicas de Net
Framework admiten asincronías Task.Run
usar y para descargar utiliza Task.Run
(como en su ejemplo). El único caso en el que realmente necesita implementar la asincronización es cuando crea una nueva llamada asíncrona (por ejemplo, construcciones de sincronización de I/O
o sincronización asincrónica).
Estos casos son extremadamente raros, por lo que en su mayoría encuentra respuestas que
"Solo lo explica con métodos ya existentes y solo usa el patrón
async/await
"
Puede profundizar en The Nature of TaskCompletionSource
TL; DR:
Task.Run()
es lo que quieres, pero ten cuidado con esconderlo en tu biblioteca.
Podría estar equivocado, pero podría estar buscando una guía para que el código CPU-Bound se ejecute de forma asíncrona [por Stephen Cleary]. También he tenido problemas para encontrar esto, y creo que la razón por la que es tan difícil es que no es lo que se supone que debes hacer para una biblioteca, un poco ...
Artículo
El artículo enlazado es una buena lectura (de 5 a 15 minutos, dependiendo) que Task.Run()
una cantidad de detalles decente sobre cómo y por Task.Run()
usar Task.Run()
como parte de una API versus usarla para no bloquear una secuencia de UI. y distingue entre dos "tipos" de procesos de larga duración que a las personas les gusta ejecutar de forma asíncrona:
- Proceso vinculado a la CPU (uno que está funcionando / realmente funciona y necesita un hilo separado para hacer su trabajo)
- Operación verdaderamente asincrónica (a veces llamada IO-bound - una que hace algunas cosas aquí y allá con un montón de tiempo de espera entre las acciones y sería mejor no acaparar un hilo mientras está sentado sin hacer nada).
El artículo toca el uso de funciones de API en varios contextos y explica si las arquitecturas asociadas "prefieren" métodos de sincronización o asincronización, y cómo una API con firmas de métodos de sincronización y asincronización "se ve" para un desarrollador.
Responder
La última sección " OK, suficiente sobre las soluciones incorrectas? ¿Cómo arreglamos esto de la manera correcta? " Entra en lo que creo que estás preguntando, terminando con esto:
Conclusión: no use Task.Run en la implementación del método; en su lugar, use Task.Run para llamar al método.
Básicamente, Task.Run()
'' Task.Run()
'' un hilo y, por lo tanto, es lo que se debe usar para el trabajo vinculado a la CPU, pero se reduce a dónde se usa. Cuando intenta hacer algo que requiere mucho trabajo y no desea bloquear el subproceso UI, use Task.Run()
para ejecutar directamente la función de trabajo duro (es decir, en el controlador de eventos o su Código basado en UI):
class MyService
{
public int CalculateMandelbrot()
{
// Tons of work to do in here!
for (int i = 0; i != 10000000; ++i)
;
return 42;
}
}
...
private async void MyButton_Click(object sender, EventArgs e)
{
await Task.Run(() => myService.CalculateMandelbrot());
}
Pero ... no oculte su Task.Run()
en una función API con el sufijo -Async
si es una función vinculada a la CPU, ya que básicamente todas -Async
funciones -Async
son verdaderamente asíncronas, y no vinculadas a la CPU.
// Warning: bad code!
class MyService
{
public int CalculateMandelbrot()
{
// Tons of work to do in here!
for (int i = 0; i != 10000000; ++i)
;
return 42;
}
public Task<int> CalculateMandelbrotAsync()
{
return Task.Run(() => CalculateMandelbrot());
}
}
En otras palabras, no llame a una función vinculada a CPU -Async
, porque los usuarios supondrán que está vinculada a IO, simplemente Task.Run()
forma asíncrona utilizando Task.Run()
, y permita que otros usuarios hagan lo mismo cuando lo consideren apropiado. Alternativamente, nombre otra cosa que tenga sentido para usted (tal vez BeginAsyncIndependentWork()
o StartIndependentWorkTask()
).