usar - when should i use await c#



Não aguardar uma chamada assíncrona ainda é assíncrona, certo? (2)

Me desculpe se esta é uma pergunta boba

Não é uma pergunta boba. É uma questão importante.

Eu tenho um colega de trabalho que afirma que isso faz a ligação para A síncrona e ele continua criando os logs do Console.WriteLine que aparentemente provam seu ponto.

Esse é o problema fundamental ali mesmo, e você precisa educar seu colega de trabalho para que ele pare de enganar a si e aos outros. Não existe uma chamada assíncrona . A chamada não é assíncrona, nunca . Diga comigo. As chamadas não são assíncronas em C # . Em C #, quando você chama uma função, essa função é chamada imediatamente após todos os argumentos serem calculados .

Se seu colega de trabalho ou você acredita que existe uma chamada assíncrona, você está enfrentando um mundo de dor porque suas crenças sobre como a assincronia funciona estarão muito desconectadas da realidade.

Então, seu colega de trabalho está correto? Claro que eles são. A chamada para A é síncrona porque todas as chamadas de função são síncronas . Mas o fato de eles acreditarem que existe uma "chamada assíncrona" significa que eles estão muito enganados sobre como a assincronia funciona em C #.

Se especificamente seu colega de trabalho acredita que await M() alguma forma faz a chamada para M() "assíncrona", então seu colega de trabalho tem um grande mal-entendido. await é um operador . É um operador complicado, com certeza, mas é um operador e opera com valores. await M() e var t = M(); await t; var t = M(); await t; são a mesma coisa . A espera ocorre após a chamada porque a await opera com o valor retornado . await NÃO é uma instrução para o compilador "gerar uma chamada assíncrona para M ()" ou qualquer outra coisa; não existe uma "chamada assíncrona".

Se essa é a natureza de suas falsas crenças, você tem a oportunidade de educar seu colega de trabalho sobre o que significa await . await significa algo simples, mas poderoso. Isso significa:

  • Veja a Task que estou operando.
  • Se a tarefa for concluída excepcionalmente, lance essa exceção
  • Se a tarefa for concluída normalmente, extraia esse valor e use-o
  • Se a tarefa estiver incompleta, inscreva o restante deste método como a continuação da tarefa esperada e retorne uma nova Task representando o fluxo de trabalho assíncrono incompleto desta chamada ao meu chamador .

Isso é tudo o que await faz. Ele apenas examina o conteúdo de uma tarefa e, se a tarefa estiver incompleta, diz "bem, não podemos progredir nesse fluxo de trabalho até que a tarefa seja concluída, então retorne ao meu interlocutor que encontrará outra coisa para esta CPU. façam".

a natureza do código dentro de A não muda apenas porque não a aguardamos.

Está correto. Chamamos síncrona A e ele retorna uma Task . O código após o site de chamada não é executado até que A retorne. O interessante de A é que A tem permissão para retornar uma Task incompleta ao Task pela chamada e essa tarefa representa um nó em um fluxo de trabalho assíncrono . O fluxo de trabalho é assíncrono e, como você observa, não faz diferença para A que você faz com seu valor de retorno depois que ele retorna; A não tem idéia se você await a Task retornada ou não. A apenas é executado o máximo que pode e, em seguida, ele retorna uma tarefa concluída normalmente, ou uma tarefa concluída excepcionalmente, ou retorna uma tarefa incompleta. Mas nada que você faça no site de chamada muda isso.

Como o valor de retorno de A não é necessário, não há necessidade de aguardar a tarefa no local da chamada

Corrigir.

não há necessidade de aguardar a tarefa no local da chamada, desde que alguém da cadeia o aguarde (o que acontece em C).

Agora você me perdeu. Por que alguém precisa aguardar a Task retornada por A ? Diga por que você acredita que alguém é obrigado a await essa Task , porque você pode ter uma crença falsa.

Meu colega de trabalho é muito insistente e comecei a duvidar de mim mesmo. Meu entendimento está errado?

Seu colega de trabalho está quase certamente errado. Sua análise parece correta até o ponto em que você diz que há um requisito para que todas as Task sejam await , o que não é verdade. É estranho não await uma Task porque significa que você escreveu um programa em que iniciou uma operação e não se importa com quando ou como ela é concluída, e certamente cheira mal escrever um programa como esse, mas não há um requisito await cada Task . Se você acredita que há, novamente, diga qual é essa crença e nós resolveremos isso.

https://src-bin.com

Sinto muito se esta é uma pergunta boba (ou duplicada).

Eu tenho uma função A :

public async Task<int> A(/* some parameters */)
{
    var result = await SomeOtherFuncAsync(/* some other parameters */);

    return (result);
}

Eu tenho outra função B , chamando A mas não usando o valor de retorno:

public Task B(/* some parameters */)
{
    var taskA = A(/* parameters */); // #1

    return (taskA);
}

Observe que B não é declarado async e não está aguardando a chamada para A A chamada para A não é uma chamada de acionar e esquecer - B é chamada por C maneira:

public async Task C()
{
    await B(/* parameters */);
}

Observe que em # 1 , não há await . Eu tenho um colega de trabalho que afirma que isso faz a ligação para A síncrona e ele continua Console.WriteLine logs do Console.WriteLine que aparentemente provam seu ponto.

Tentei salientar que, só porque não esperamos os resultados dentro de B , a cadeia de tarefas é aguardada e a natureza do código dentro de A não muda apenas porque não esperamos. Como o valor de retorno de A não é necessário, não há necessidade de aguardar a tarefa no local da chamada, desde que alguém da cadeia o aguarde (o que acontece em C ).

Meu colega de trabalho é muito insistente e comecei a duvidar de mim mesmo. Meu entendimento está errado?


Answer #1

Você está certo. Criar uma tarefa faz apenas isso e não importa quando e quem aguardará seu resultado. Tente colocar em await Task.Delay(veryBigNumber); em SomeOtherFuncAsync e a saída do console deve ser o que você esperaria.

Isso se chama eliding e eu sugiro que você leia este post do blog , onde você pode ver por que você deve ou não fazer isso.

Também algum exemplo mínimo (pouco complicado) de copiar seu código, provando que você está certo:

class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine($"Start of main {Thread.CurrentThread.ManagedThreadId}");
            var task = First();
            Console.WriteLine($"Middle of main {Thread.CurrentThread.ManagedThreadId}");
            await task;
            Console.WriteLine($"End of main {Thread.CurrentThread.ManagedThreadId}");
        }

        static Task First()
        {
            return SecondAsync();
        }

        static async Task SecondAsync()
        {
            await ThirdAsync();
        }

        static async Task ThirdAsync()
        {
            Console.WriteLine($"Start of third {Thread.CurrentThread.ManagedThreadId}");
            await Task.Delay(1000);
            Console.WriteLine($"End of third {Thread.CurrentThread.ManagedThreadId}");
        }
    }

Isso escreve Middle of main antes de End of third , provando que é de fato assíncrono. Além disso, você pode (provavelmente) ver que o final das funções é executado em um segmento diferente do restante do programa. Tanto o começo quanto o meio do main sempre serão executados no mesmo encadeamento, porque esses são, de fato, sincronizados (o main inicia, chama a cadeia de funções, o terceiro retorna (pode retornar na linha com a palavra-chave await ) e o main continua como se houvesse nunca houve nenhuma função assíncrona envolvida .. As terminações após as palavras-chave await em ambas as funções podem ser executadas em qualquer thread no ThreadPool (ou no contexto de sincronização que você está usando).

Agora, é interessante notar que, se o Task.Delay in Third não Task.Delay muito e realmente terminasse de forma síncrona, tudo isso seria executado em um único encadeamento. Além disso, apesar de ser executado de forma assíncrona, ele pode ser executado em um único encadeamento. Não há regra declarando que uma função assíncrona use mais de um encadeamento; pode muito bem fazer algum outro trabalho enquanto aguarda a conclusão de alguma tarefa de E / S.





async-await