Eu sou novo na programação assíncrona com o async
modificador. Estou tentando descobrir como garantir que meu Main
método de aplicativo de console seja executado de forma assíncrona.
class Program
{
static void Main(string[] args)
{
Bootstrapper bs = new Bootstrapper();
var list = bs.GetList();
}
}
public class Bootstrapper {
public async Task<List<TvChannel>> GetList()
{
GetPrograms pro = new GetPrograms();
return await pro.DownloadTvChannels();
}
}
Eu sei que isso não está sendo executado de forma assíncrona a partir do "topo". Como não é possível especificar o async
modificador no Main
método, como posso executar o código de main
forma assíncrona?
c#
.net
asynchronous
console-application
danielovich
fonte
fonte
Respostas:
Como você descobriu, no VS11, o compilador não permitirá um
async Main
método. Isso foi permitido (mas nunca recomendado) no VS2010 com o CTP assíncrono.Tenho postagens recentes no blog sobre os programas de console assíncrono / aguardado e assíncrono em particular. Aqui estão algumas informações básicas da publicação de introdução:
Veja por que esse problema ocorre nos programas de console com um
async Main
:Uma solução é fornecer seu próprio contexto - um "loop principal" para o seu programa de console compatível com async.
Se você tiver uma máquina com o CTP assíncrono, poderá usar
GeneralThreadAffineContext
em Meus Documentos \ Microsoft Visual Studio CTP \ Amostras assíncronas \ Amostras (teste de C #) Testing \ AsyncTestUtilities . Alternativamente, você pode usarAsyncContext
a partir de meu pacote Nito.AsyncEx NuGet .Aqui está um exemplo usando
AsyncContext
;GeneralThreadAffineContext
tem uso quase idêntico:Como alternativa, você pode apenas bloquear o segmento principal do console até que seu trabalho assíncrono seja concluído:
Observe o uso de
GetAwaiter().GetResult()
; isso evita oAggregateException
acondicionamento que acontece se você usarWait()
ouResult
.Atualização, 30/11/2017: A partir da Atualização 3 do Visual Studio 2017 (15.3), o idioma agora suporta um
async Main
- desde que retorneTask
ouTask<T>
. Agora você pode fazer isso:A semântica parece ser a mesma do
GetAwaiter().GetResult()
estilo de bloqueio do encadeamento principal. No entanto, ainda não há especificação de idioma para o C # 7.1, portanto, isso é apenas uma suposição.fonte
Wait
ouResult
, e não há nada de errado nisso. Mas esteja ciente de que existem duas diferenças importantes: 1) todas asasync
continuações são executadas no conjunto de encadeamentos, e não no encadeamento principal, e 2) todas as exceções são agrupadas em umAggregateException
.<LangVersion>latest</LangVersion>
ao arquivo csproj, como mostrado aqui .Você pode resolver isso com essa construção simples:
Isso colocará tudo o que você faz no ThreadPool onde você o deseja (para que outras tarefas iniciadas / aguardadas não tentem voltar a um segmento que não deveriam) e espere até que tudo esteja pronto antes de fechar o aplicativo do console. Não há necessidade de loops especiais ou bibliotecas externas.
Edit: Incorpore a solução de Andrew para Exceções não capturadas.
fonte
Wait()
comGetAwaiter().GetResult()
você vai evitar oAggregateException
invólucro quando as coisas jogar.async main
está sendo introduzido no C # 7.1, até o momento em que este artigo foi escrito.Você pode fazer isso sem precisar de bibliotecas externas também fazendo o seguinte:
fonte
getListTask.Result
também é uma chamada de bloqueio e, portanto, o código acima pode ser escrito semTask.WaitAll(getListTask)
.GetList
jogar, você terá que pegar umAggregateException
e interrogar suas exceções para determinar a exceção real lançada. Você pode, no entanto, ligarGetAwaiter()
para obter o valorTaskAwaiter
paraTask
, e chamarGetResult()
isso, ievar list = getListTask.GetAwaiter().GetResult();
. Ao obter o resultado daTaskAwaiter
(também uma chamada de bloqueio), as exceções lançadas não serão agrupadas em umAggregateException
.No C # 7.1, você poderá fazer um Main assíncrono adequado . As assinaturas apropriadas para o
Main
método foram estendidas para:Por exemplo, você poderia estar fazendo:
Em tempo de compilação, o método do ponto de entrada assíncrono será convertido para chamar
GetAwaitor().GetResult()
.Detalhes: https://blogs.msdn.microsoft.com/mazhou/2017/05/30/c-7-series-part-2-async-main
EDITAR:
Para habilitar os recursos de linguagem C # 7.1, clique com o botão direito do mouse no projeto e clique em "Propriedades" e, em seguida, vá para a guia "Compilar". Lá, clique no botão avançado na parte inferior:
No menu suspenso da versão do idioma, selecione "7.1" (ou qualquer valor mais alto):
O padrão é a "versão principal mais recente", que seria avaliada (no momento da redação deste documento) para o C # 7.0, que não suporta o principal assíncrono nos aplicativos de console.
fonte
Vou adicionar um recurso importante que todas as outras respostas ignoraram: cancelamento.
Uma das grandes vantagens do TPL é o suporte ao cancelamento, e os aplicativos de console têm um método de cancelamento incorporado (CTRL + C). É muito simples uni-los. É assim que eu estruturo todos os meus aplicativos de console assíncrono:
fonte
Wait()
?Wait()
lo para o arquivo, ele não esperará o código assíncrono terminar - ele parará de esperar e encerrará o processo imediatamente.Wait()
método recebe o mesmo token. O que estou tentando dizer é que isso não parece fazer nenhuma diferença.O C # 7.1 (usando vs 2017 atualização 3) apresenta o principal assíncrono
Você pode escrever:
Para obter mais detalhes Série C # 7, Parte 2: Principal assíncrono
Atualizar:
Você pode receber um erro de compilação:
Esse erro ocorre porque o vs2017.3 está configurado por padrão como c # 7.0 e não c # 7.1.
Você deve modificar explicitamente a configuração do seu projeto para definir os recursos do c # 7.1.
Você pode definir o c # 7.1 por dois métodos:
Método 1: usando a janela de configurações do projeto:
Método2: Modificar PropertyGroup de .csproj manualmente
Adicione esta propriedade:
exemplo:
fonte
Se você estiver usando o C # 7.1 ou posterior, siga a resposta do nawfal e altere o tipo de retorno do seu método Main para
Task
ouTask<int>
. Se você não está:async Task MainAsync
como Johan disse ..GetAwaiter().GetResult()
para capturar a exceção subjacente, como do0g disse .CTRL+C
deve encerrar o processo imediatamente. (Obrigado binki !)OperationCancelledException
- retorna um código de erro apropriado.O código final se parece com:
fonte
e.Cancel = true
é incondicional.Ainda não precisei muito disso, mas quando usei o aplicativo de console para testes rápidos e assíncrono necessário, resolvi-o assim:
fonte
SynchronizationContext
associado ao encadeamento principal. Portanto, não haverá impasse porque, mesmo semConfigureAwait(false)
, todas as continuações serão executadas no pool de threads.Para chamar tarefas assíncronas de Main, use
Task.Run () para .NET 4.5
Task.Factory.StartNew () para .NET 4.0 (pode exigir a biblioteca Microsoft.Bcl.Async para async e aguardar palavras-chave)
Detalhes: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
fonte
Em Main, tente alterar a chamada para GetList para:
fonte
Quando o CTP C # 5 foi introduzido, você certamente pode marcar Main como
async
... embora geralmente não seja uma boa ideia fazê-lo. Acredito que isso foi alterado pelo lançamento do VS 2013 para se tornar um erro.A menos que você tenha iniciado outros threads em primeiro plano , seu programa será encerrado quando for
Main
concluído, mesmo que tenha sido iniciado algum trabalho em segundo plano.O que você realmente está tentando fazer? Observe que seu
GetList()
método realmente não precisa ser assíncrono no momento - está adicionando uma camada extra sem motivo real. É logicamente equivalente a (mas mais complicado que):fonte
DownloadTvChannels()
retorna? Presumivelmente, ele retorna umTask<List<TvChannel>>
, não é? Caso contrário, é improvável que você possa aguardar. (Possível, dado o padrão do garçom, mas improvável.) Quanto aoMain
método - ele ainda precisa ser estático ... você substituiu ostatic
modificador peloasync
modificador, talvez?public static async void Main() {}
? Mas seDownloadTvChannels()
já retorna umTask<List<TvChannel>>
, presumivelmente já é assíncrono - para que você não precise adicionar outra camada. Vale a pena entender isso com cuidado.A versão mais recente do C # - C # 7.1 permite criar um aplicativo de console assíncrono. Para habilitar o C # 7.1 no projeto, você deve atualizar seu VS para pelo menos 15.3 e alterar a versão do C # para
C# 7.1
ouC# latest minor version
. Para fazer isso, vá para Propriedades do projeto -> Compilar -> Avançado -> Versão do idioma.Depois disso, o seguinte código funcionará:
fonte
No MSDN, a documentação do método Task.Run (Action) fornece este exemplo que mostra como executar um método de forma assíncrona em
main
:Observe esta declaração que segue o exemplo:
Portanto, se você deseja que a tarefa seja executada no encadeamento principal do aplicativo, consulte a resposta em @StephenCleary .
E sobre o segmento em que a tarefa é executada, observe também o comentário de Stephen em sua resposta:
(Consulte Tratamento de exceções (Task Parallel Library) para saber como incorporar o tratamento de exceções para lidar com um
AggregateException
.)Finalmente, no MSDN da documentação do método Task.Delay (TimeSpan) , este exemplo mostra como executar uma tarefa assíncrona que retorna um valor:
Observe que, em vez de passar
delegate
paraTask.Run
, você pode passar uma função lambda como esta:fonte
Para evitar o congelamento quando você chama uma função em algum lugar da pilha de chamadas que tenta ingressar novamente no segmento atual (que está preso em uma espera), faça o seguinte:
(o elenco é necessário apenas para resolver ambiguidade)
fonte
No meu caso, eu tinha uma lista de tarefas que eu queria executar em assíncrona a partir do meu método principal, tenho usado isso na produção há algum tempo e funciona bem.
fonte