Estou trabalhando em um projeto de rede multitarefa e sou novo Threading.Tasks
. Implementei um simples Task.Factory.StartNew()
e gostaria de saber como posso fazer isso Task.Run()
?
Aqui está o código básico:
Task.Factory.StartNew(new Action<object>(
(x) =>
{
// Do something with 'x'
}), rawData);
Eu olhei System.Threading.Tasks.Task
no Pesquisador de objetos e não consegui encontrar um Action<T>
parâmetro semelhante. Existe apenas Action
aquele que leva void
parâmetro e nenhum tipo .
Existem apenas 2 coisas semelhantes: static Task Run(Action action)
e static Task Run(Func<Task> function)
mas não pode postar parâmetro (s) com ambos.
Sim, eu sei que posso criar um método de extensão simples para ele, mas minha principal questão é podemos escrevê-lo em uma única linha com Task.Run()
?
c#
lambda
task-parallel-library
task
MFatihMAR
fonte
fonte
rawData
é um pacote de dados de rede que tem uma classe de contêiner (como DataPacket) e estou reutilizando essa instância para reduzir a pressão do GC. Portanto, se eu usarrawData
diretamente noTask
, ele pode (provavelmente) ser alterado antes de serTask
manuseado. Agora, acho que posso criar outrabyte[]
instância para ele. Acho que é a solução mais simples para mim.Action<byte[]>
não muda isso.Respostas:
private void RunAsync() { string param = "Hi"; Task.Run(() => MethodWithParameter(param)); } private void MethodWithParameter(string param) { //Do stuff }
Editar
Devido à demanda popular, devo observar que o
Task
iniciado será executado em paralelo com o thread de chamada. Assumindo o padrãoTaskScheduler
, usará o .NETThreadPool
. De qualquer forma, isso significa que você precisa levar em conta quaisquer parâmetros que estão sendo passados para oTask
como potencialmente sendo acessados por vários threads de uma vez, tornando-os um estado compartilhado. Isso inclui acessá-los no thread de chamada.No meu código acima, esse caso é inteiramente discutível. Strings são imutáveis. É por isso que os usei como exemplo. Mas digamos que você não esteja usando um
String
...Uma solução é usar
async
eawait
. Isso, por padrão, irá capturar oSynchronizationContext
do thread de chamada e criar uma continuação para o resto do método após a chamadaawait
e anexá-lo ao criadoTask
. Se esse método estiver sendo executado no thread da GUI do WinForms, ele será do tipoWindowsFormsSynchronizationContext
.A continuação será executada após ser postada de volta no capturado
SynchronizationContext
- novamente apenas por padrão. Assim, você estará de volta ao tópico com o qual começou após aawait
ligação. Você pode alterar isso de várias maneiras, principalmente usandoConfigureAwait
. Em suma, o resto do que o método não continuará até que após aTask
completou em outro segmento. Mas o thread de chamada continuará a ser executado em paralelo, mas não o resto do método.Essa espera para concluir a execução do resto do método pode ou não ser desejável. Se nada nesse método acessar posteriormente os parâmetros passados para o,
Task
você pode não querer mais usarawait
.Ou talvez você use esses parâmetros muito mais tarde no método. Não há razão para isso
await
imediatamente, pois você pode continuar fazendo o trabalho com segurança. Lembre-se de que você pode armazenar oTask
retornado em uma variável eawait
posteriormente - até mesmo no mesmo método. Por exemplo, uma vez que você precisa acessar os parâmetros passados com segurança depois de fazer um monte de outro trabalho. Novamente, você não precisa estarawait
àTask
direita ao executá-lo.De qualquer forma, uma maneira simples de tornar esse thread-safe com relação aos parâmetros passados para
Task.Run
é fazer isso:Você deve primeiro decorar
RunAsync
comasync
:private async void RunAsync()
Nota importante
De preferência, o método marcado não deve retornar void, como a documentação vinculada menciona. A exceção comum são os manipuladores de eventos, como cliques em botões e outros. Eles devem retornar vazios. Caso contrário, sempre tento retornar um ou ao usar . É uma boa prática por vários motivos.
async
Task
Task<TResult>
async
Agora você pode
await
executar o exemploTask
abaixo. Você não pode usarawait
semasync
.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
Portanto, em geral, se você fizer
await
a tarefa, poderá evitar tratar os parâmetros passados como um recurso potencialmente compartilhado com todas as armadilhas de modificar algo de vários threads de uma vez. Além disso, tome cuidado com os fechamentos . Não vou cobri-los em profundidade, mas o artigo vinculado faz um ótimo trabalho.Nota
Um pouco fora do assunto, mas tome cuidado ao usar qualquer tipo de "bloqueio" no thread da GUI do WinForms devido a ele estar marcado com
[STAThread]
. O usoawait
não bloqueia de forma alguma, mas às vezes vejo que é usado em conjunto com algum tipo de bloqueio."Block" está entre aspas porque você tecnicamente não pode bloquear o thread da GUI do WinForms . Sim, se você usar
lock
o thread da GUI do WinForms, ele ainda enviará mensagens, apesar de você pensar que está "bloqueado". Não é.Isso pode causar problemas bizarros em casos muito raros. Uma das razões pelas quais você nunca quer usar um
lock
ao pintar, por exemplo. Mas esse é um caso marginal e complexo; no entanto, eu já vi isso causar problemas malucos. Então, eu anotei isso por uma questão de integridade.fonte
Task.Run(() => MethodWithParameter(param));
. O que significa que separam
for modificado após oTask.Run
, você pode ter resultados inesperados noMethodWithParameter
.Use a captura de variáveis para "passar" os parâmetros.
var x = rawData; Task.Run(() => { // Do something with 'x' });
Você também pode usar
rawData
diretamente, mas deve ter cuidado, se alterar o valor derawData
fora de uma tarefa (por exemplo, um iterador em umfor
loop), também mudará o valor dentro da tarefa.fonte
Task.Run
.Sei que este é um tópico antigo, mas queria compartilhar uma solução que acabei tendo que usar, pois a postagem aceita ainda tem um problema.
O problema:
Conforme apontado por Alexandre Severino, se
param
(na função abaixo) mudar logo após a chamada da função, você pode obter algum comportamento inesperado emMethodWithParameter
.Minha solução:
Para explicar isso, acabei escrevendo algo mais parecido com a seguinte linha de código:
(new Func<T, Task>(async (p) => await Task.Run(() => MethodWithParam(p)))).Invoke(param);
Isso me permitiu usar o parâmetro com segurança de forma assíncrona, apesar do fato de que o parâmetro mudou muito rapidamente após iniciar a tarefa (o que causou problemas com a solução postada).
Usando essa abordagem,
param
(tipo de valor) obtém seu valor transmitido, portanto, mesmo se o método assíncrono for executado após asparam
alterações,p
terá o valor queparam
tinha quando esta linha de código foi executada.fonte
var localParam = param; await Task.Run(() => MethodWithParam(localParam));
A partir de agora você também pode:
Action<int> action = (o) => Thread.Sleep(o); int param = 10; await new TaskFactory().StartNew(action, param)
fonte
Basta usar Task.Run
var task = Task.Run(() => { //this will already share scope with rawData, no need to use a placeholder });
Ou, se você gostaria de usá-lo em um método e aguardar a tarefa mais 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; }
fonte
for(int rawData = 0; rawData < 10; ++rawData) { Task.Run(() => { Console.WriteLine(rawData); } ) }
, não vai se comportar da mesma forma como serawData
fosse passado como no exemplo StartNew do OP.Não está claro se o problema original era o mesmo que eu: querer maximizar os threads da CPU na computação dentro de um loop enquanto preserva o valor do iterador e mantém em linha para evitar passar uma tonelada de variáveis para uma função de trabalho.
for (int i = 0; i < 300; i++) { Task.Run(() => { var x = ComputeStuff(datavector, i); // value of i was incorrect var y = ComputeMoreStuff(x); // ... }); }
Eu fiz isso funcionar alterando o iterador externo e localizando seu valor com uma porta.
for (int ii = 0; ii < 300; ii++) { System.Threading.CountdownEvent handoff = new System.Threading.CountdownEvent(1); Task.Run(() => { int i = ii; handoff.Signal(); var x = ComputeStuff(datavector, i); var y = ComputeMoreStuff(x); // ... }); handoff.Wait(); }
fonte
A ideia é evitar o uso de um sinal como o acima. Colocar valores int em uma estrutura evita que esses valores sejam alterados (na estrutura). Eu tive o seguinte problema: o loop var i mudaria antes de DoSomething (i) ser chamado (i foi incrementado no final do loop antes de () => DoSomething (i, i i) ser chamado). Com as estruturas isso não acontece mais. Bug desagradável para encontrar: DoSomething (i, i i) parece ótimo, mas nunca tenho certeza se ele é chamado a cada vez com um valor diferente para i (ou apenas 100 vezes com i = 100), portanto -> struct
struct Job { public int P1; public int P2; } … for (int i = 0; i < 100; i++) { var job = new Job { P1 = i, P2 = i * i}; // structs immutable... Task.Run(() => DoSomething(job)); }
fonte