Tarea.Ejecutar con Parámetro(s)?
Estoy trabajando en un proyecto de red multitarea y soy nuevo en Threading.Tasks
. He implementado un simple Task.Factory.StartNew()
y me pregunto ¿cómo puedo hacerlo con Task.Run()
?
Aquí está el código básico:
Task.Factory.StartNew(new Action<object>(
(x) =>
{
// Do something with 'x'
}), rawData);
Miré en System.Threading.Tasks.Task
en Explorador de objetos y no pude encontrar un parámetro similar a Action<T>
. Solo hay Action
que toma void
parámetro y no tipo.
Solo hay 2 cosas similares: static Task Run(Action action)
y static Task Run(Func<Task> function)
pero no se pueden publicar parámetros con ambos.
Sí, lo sé Puedo crear un método de extensión simple para él, pero mi pregunta principal es ¿podemos escribirlo en una sola línea con Task.Run()
?
5 answers
private void RunAsync()
{
string param = "Hi";
Task.Run(() => MethodWithParameter(param));
}
private void MethodWithParameter(string param)
{
//Do stuff
}
Editar
Debido a la demanda popular debo tener en cuenta que el Task
lanzado se ejecutará en paralelo con el hilo de llamada. Asumiendo el valor predeterminado TaskScheduler
esto utilizará el.NET ThreadPool
. De todos modos, esto significa que necesita tener en cuenta cualquier parámetro(s) que se pasa a la Task
como potencialmente se accede por múltiples hilos a la vez, haciéndolos estado compartido. Esto incluye acceder a ellos en el hilo de llamada.
En mi código anterior ese caso se hace enteramente discutible. Las cadenas son inmutables. Por eso los usé como ejemplo. Pero digamos que no estás usando un String
...
Una solución es usar async
y await
. Esto, por defecto, capturará el SynchronizationContext
del hilo de llamada y creará una continuación para el resto del método después de la llamada a await
y lo adjuntará al Task
creado. Si este método se está ejecutando en el subproceso de la GUI de WinForms, será de tipo WindowsFormsSynchronizationContext
.
La continuación se ejecutará después de ser publicado de nuevo a la captured SynchronizationContext
- de nuevo solo por defecto. Así que volverás al hilo con el que empezaste después de la llamada await
. Puede cambiar esto de varias maneras, especialmente usando ConfigureAwait
. En resumen, el resto de ese método no continuará hasta después de que el Task
se haya completado en otro hilo. Pero el hilo de llamada continuará ejecutándose en paralelo, solo que no el resto del método.
Esta espera para completar la ejecución del resto del método puede o no ser deseable. Si nada en ese método accede más tarde a los parámetros pasados a Task
es posible que no desee usar await
en absoluto.
O tal vez use esos parámetros mucho más adelante en el método. No hay razón para await
inmediatamente ya que podría continuar con seguridad haciendo el trabajo. Recuerde, puede almacenar el Task
devuelto en una variable y await
en él más tarde, incluso en el mismo método. Por ejemplo, una vez que necesite acceder a los parámetros pasados de forma segura después de hacer un montón de otro trabajo. Una vez más, lo haces no necesita await
en el Task
justo cuando lo ejecuta.
De todos modos, una forma sencilla de hacer este hilo seguro con respecto a los parámetros pasados a Task.Run
es hacer esto:
Primero debes decorar RunAsync
con async
:
private async void RunAsync()
Nota Importante
Preferiblemente el método marcado async
no debería devolver void, como menciona la documentación enlazada. La excepción común a esto son los controladores de eventos, como los clics de botón y tal. Deben regresar vacíos. De lo contrario, siempre intento devolver un Task
o Task<TResult>
cuando uso async
. Es una buena práctica por varias razones.
Ahora puedes await
ejecutar el Task
como a continuación. No se puede usar await
sin async
.
await Task.Run(() => MethodWithParameter(param));
//Code here and below in the same method will not run until AFTER the above task has completed in one fashion or another
Entonces, en general, si await
la tarea puede evitar tratar los parámetros pasados como un recurso potencialmente compartido con todas las trampas de modificar algo de varios subprocesos a la vez. También, cuidado concierres . No los cubriré en profundidad, pero el artículo vinculado hace un gran trabajo.
Nota al margen
Un poco fuera de tema, pero tenga cuidado de usar cualquier tipo de "bloqueo" en el hilo de la GUI de WinForms debido a que está marcado con [STAThread]
. Usar await
no bloqueará en absoluto, pero a veces veo que se usa junto con algún tipo de bloqueo.
"Bloquear" está entre comillas porque técnicamente no puede bloquear el hilo de la GUI de WinForms. Sí, si utiliza lock
en el WinForms GUI thread it seguirá bombeando mensajes, a pesar de que pienses que está "bloqueado". No lo es.
Esto puede causar problemas extraños en casos muy raros. Una de las razones por las que nunca quieres usar un lock
al pintar, por ejemplo. Pero ese es un caso marginal y complejo; sin embargo, he visto que causa problemas locos. Así que lo anoté por completo.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-18 01:44:18
Utilice la captura de variables para "pasar" parámetros.
var x = rawData;
Task.Run(() =>
{
// Do something with 'x'
});
También puede usar rawData
directamente, pero debe tener cuidado, si cambia el valor de rawData
fuera de una tarea (por ejemplo, un iterador en un bucle for
) también cambiará el valor dentro de la tarea.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-13 21:33:40
Simplemente use Task.Run
var task = Task.Run(() =>
{
//this will already share scope with rawData, no need to use a placeholder
});
O, si desea usarlo en un método y esperar la tarea más tarde
public Task<T> SomethingAsync<T>()
{
var task = Task.Run(() =>
{
//presumably do something which takes a few ms here
//this will share scope with any passed parameters in the method
return default(T);
});
return task;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-13 21:40:00
Sé que este es un hilo viejo, pero quería compartir una solución que terminé teniendo que usar ya que el mensaje aceptado todavía tiene un problema.
La cuestión:
Como señaló Alexandre Severino, si param
(en la función de abajo) cambia poco después de la llamada a la función, puede obtener algún comportamiento inesperado en MethodWithParameter
.
Task.Run(() => MethodWithParameter(param));
Mi solución:
Para explicar esto, terminé escribiendo algo más como la siguiente línea de código:
(new Func<T, Task>(async (p) => await Task.Run(() => MethodWithParam(p)))).Invoke(param);
Esto me permitió para utilizar de forma segura el parámetro de forma asíncrona a pesar del hecho de que el parámetro cambió muy rápidamente después de iniciar la tarea (lo que causó problemas con la solución publicada).
Usando este enfoque, param
(tipo de valor) obtiene su valor pasado, por lo que incluso si el método async se ejecuta después de que param
cambie, p
tendrá cualquier valor que param
tuviera cuando se ejecutó esta línea de código.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-13 20:04:25
A partir de ahora también puedes :
Action<int> action = (o) => Thread.Sleep(o);
int param = 10;
await new TaskFactory().StartNew(action, param)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-08 20:53:58