Como chamar o método assíncrono do método síncrono em c #?

863

Eu tenho um public async void Foo()método que eu quero chamar do método síncrono. Até agora, tudo o que vi na documentação do MSDN está chamando métodos assíncronos por métodos assíncronos, mas todo o meu programa não é construído com métodos assíncronos.

Isso é possível?

Aqui está um exemplo de chamada desses métodos de um método assíncrono: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx

Agora, estou tentando chamar esses métodos assíncronos a partir de métodos de sincronização.

Torre
fonte
2
Eu encontrei isso também. Substituindo um RoleProvider, você não pode alterar a assinatura do método GetRolesForUser, para não tornar o método assíncrono e, portanto, não usar Aguardar para chamar a API de forma assíncrona. Minha solução temporária foi adicionar métodos síncronos à minha classe HttpClient genérica, mas gostaria de saber se isso é possível (e quais podem ser as implicações).
Timothy Lee Russell
1
Como seu async void Foo()método não retorna a Task, significa que um chamador não pode saber quando é concluído, ele deve retornar Task.
Dai
1
Vinculando um q / a relacionado sobre como fazer isso em um thread da interface do usuário.
Noseratio 29/11

Respostas:

711

A programação assíncrona "cresce" através da base de código. Foi comparado a um vírus zumbi . A melhor solução é permitir que ela cresça, mas às vezes isso não é possível.

Escrevi alguns tipos na minha biblioteca Nito.AsyncEx para lidar com uma base de código parcialmente assíncrona. Porém, não há solução que funcione em todas as situações.

Solução A

Se você possui um método assíncrono simples que não precisa ser sincronizado de volta ao seu contexto, pode usar Task.WaitAndUnwrapException:

var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();

Você não deseja usar Task.Waitou Task.Resultporque eles envolvem exceções AggregateException.

Esta solução é apropriada apenas se MyAsyncMethodnão for sincronizada de volta ao seu contexto. Em outras palavras, todo awaitem MyAsyncMethoddeve terminar com ConfigureAwait(false). Isso significa que não é possível atualizar nenhum elemento da interface do usuário nem acessar o contexto de solicitação do ASP.NET.

Solução B

Se MyAsyncMethodprecisar sincronizar de volta ao seu contexto, você poderá usar AsyncContext.RunTaskpara fornecer um contexto aninhado:

var result = AsyncContext.RunTask(MyAsyncMethod).Result;

* Atualização 14/04/2014: nas versões mais recentes da biblioteca, a API é a seguinte:

var result = AsyncContext.Run(MyAsyncMethod);

(Não há problema em usar Task.Resultneste exemplo, porque RunTaskpropagará Taskexceções).

O motivo pelo qual você pode precisar, em AsyncContext.RunTaskvez de, Task.WaitAndUnwrapExceptioné por causa de uma possibilidade bastante sutil de conflito que ocorre no WinForms / WPF / SL / ASP.NET:

  1. Um método síncrono chama um método assíncrono, obtendo a Task.
  2. O método síncrono faz uma espera de bloqueio no Task.
  3. O asyncmétodo usa awaitsem ConfigureAwait.
  4. O Tasknão pode ser concluído nessa situação porque ele é concluído apenas quando o asyncmétodo é concluído; o asyncmétodo não pode ser concluído porque está tentando agendar sua continuação para SynchronizationContext, e WinForms / WPF / SL / ASP.NET não permitirá que a continuação seja executada porque o método síncrono já está sendo executado nesse contexto.

Essa é uma das razões pelas quais é uma boa ideia usar o máximo possível ConfigureAwait(false)em todos os asyncmétodos.

Solução C

AsyncContext.RunTasknão funcionará em todos os cenários. Por exemplo, se o asyncmétodo aguardar algo que exija a conclusão de um evento da interface do usuário, você entrará em conflito mesmo com o contexto aninhado. Nesse caso, você pode iniciar o asyncmétodo no pool de threads:

var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();

No entanto, esta solução requer um MyAsyncMethodque funcione no contexto do conjunto de encadeamentos. Portanto, ele não pode atualizar elementos da interface do usuário nem acessar o contexto de solicitação do ASP.NET. E, nesse caso, você também pode adicionar ConfigureAwait(false)suas awaitdeclarações e usar a solução A.

Atualização, 01/05/2019: As "práticas menos recomendadas" atuais estão em um artigo do MSDN aqui .

Stephen Cleary
fonte
9
A solução A parece o que eu quero, mas parece que task.WaitAndUnwrapException () não chegou ao .Net 4.5 RC; só possui task.Wait (). Alguma idéia de como fazer isso com a nova versão? Ou esse é um método de extensão personalizado que você escreveu?
deadlydog
3
WaitAndUnwrapExceptioné meu próprio método da minha biblioteca AsyncEx . As bibliotecas oficiais do .NET não fornecem muita ajuda para misturar códigos de sincronização e assíncrono (e, em geral, você não deve fazê-lo!). Estou aguardando o .NET 4.5 RTW e um novo laptop que não seja XP antes de atualizar o AsyncEx para rodar no 4.5 (atualmente não posso desenvolver o 4.5 porque estou preso no XP por mais algumas semanas).
22412 Stephen Hawy
12
AsyncContextagora tem um Runmétodo que leva uma expressão lambda, então você deve usarvar result = AsyncContext.Run(() => MyAsyncMethod());
Stephen Cleary
1
Tirei sua biblioteca do Nuget, mas na verdade não parece ter um RunTaskmétodo. A coisa mais próxima que pude encontrar foi Run, mas isso não tem uma Resultpropriedade.
Asad Saeeduddin
3
@Asad: Sim, mais de 2 anos depois, a API mudou. Agora você pode simplesmente dizervar result = AsyncContext.Run(MyAsyncMethod);
Stephen Cleary
313

Adicionando uma solução que finalmente resolveu meu problema, espero poupar o tempo de alguém.

Leia primeiro alguns artigos de Stephen Cleary :

Das "duas melhores práticas" em "Não bloqueie no código assíncrono", a primeira não funcionou para mim e a segunda não foi aplicável (basicamente, se eu posso usar await, eu faço!).

Então, aqui está minha solução alternativa: encerre a chamada dentro de um Task.Run<>(async () => await FunctionAsync());e esperamos que não haja mais impasse .

Aqui está o meu código:

public class LogReader
{
    ILogger _logger;

    public LogReader(ILogger logger)
    {
        _logger = logger;
    }

    public LogEntity GetLog()
    {
        Task<LogEntity> task = Task.Run<LogEntity>(async () => await GetLogAsync());
        return task.Result;
    }

    public async Task<LogEntity> GetLogAsync()
    {
        var result = await _logger.GetAsync();
        // more code here...
        return result as LogEntity;
    }
}
Esconder
fonte
5
Dois anos depois, estou curioso para saber como esta solução está se sustentando. Alguma novidade? Existe sutileza nessa abordagem que é perdida nos iniciantes?
Dan Esparza
26
Isso não resultará em conflito, é verdade, mas simplesmente porque é forçado a executar em um novo thread, fora do contexto de sincronização do thread de origem. No entanto, há certos ambientes nos quais isso é muito desaconselhável: principalmente aplicativos da web. Isso poderia reduzir pela metade os encadeamentos disponíveis para o servidor da Web (um encadeamento para a solicitação e um para este). Quanto mais você faz isso, pior fica. Você pode acabar bloqueando todo o servidor da web.
22817 Chris Pratt
30
@ ChrisPratt - Você pode estar certo, porque Task.Run()não é uma prática recomendada em um código assíncrono. Mas, novamente, qual é a resposta para a pergunta original? Nunca chame um método assíncrono de forma síncrona? Nós desejamos, mas no mundo real, às vezes precisamos.
Tohid 28/10
1
@ Oh você poderia tentar a biblioteca de Stephen Cleary. Já vi pessoas presumirem isso e o Parallel.ForEachabuso não terá efeito no 'mundo real' e, eventualmente, derrubou os servidores. Esse código é válido para aplicativos de console, mas como @ChrisPratt diz, não deve ser usado em aplicativos da Web. Pode funcionar "agora", mas não é escalável.
Makhdumi
1
Estou intrigado para começar a criar nova accountsin SO responder perguntas apenas para obter pontos suficientes para upvote este ....
Giannis Paraskevopoulos
206

A Microsoft criou uma classe AsyncHelper (interna) para executar o Async como Sync. A fonte se parece com:

internal static class AsyncHelper
{
    private static readonly TaskFactory _myTaskFactory = new 
      TaskFactory(CancellationToken.None, 
                  TaskCreationOptions.None, 
                  TaskContinuationOptions.None, 
                  TaskScheduler.Default);

    public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        return AsyncHelper._myTaskFactory
          .StartNew<Task<TResult>>(func)
          .Unwrap<TResult>()
          .GetAwaiter()
          .GetResult();
    }

    public static void RunSync(Func<Task> func)
    {
        AsyncHelper._myTaskFactory
          .StartNew<Task>(func)
          .Unwrap()
          .GetAwaiter()
          .GetResult();
    }
}

As classes base Microsoft.AspNet.Identity possuem apenas métodos Async e, para chamá-las como Sincronização, existem classes com métodos de extensão que se parecem com (exemplo de uso):

public static TUser FindById<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<TUser>(() => manager.FindByIdAsync(userId));
}

public static bool IsInRole<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId, string role) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<bool>(() => manager.IsInRoleAsync(userId, role));
}

Para aqueles preocupados com os termos de código de licenciamento, aqui está um link para código muito semelhante (apenas adiciona suporte à cultura no encadeamento) que possui comentários para indicar que ele é licenciado pelo MIT pela Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs

Erik Philips
fonte
2
Meus métodos assíncronos aguardam outros métodos assíncronos. NÃO decore nenhuma das minhas awaitchamadas ConfigureAwait(false). Eu tentei usar AsyncHelper.RunSyncpara chamar uma função assíncrona da Application_Start()função Global.asax e parece funcionar. Isso significa que AsyncHelper.RunSyncnão é propenso ao problema de impasse "marechal de volta ao contexto do chamador" que eu li sobre algum outro lugar nesta postagem?
Bob.at.Indigo.Health
1
@ Bob.at.SBS depende do que você codifica. Não é tão simples como se eu usasse esse código, estou seguro . Essa é uma maneira muito mínima e semi-segura de executar comandos assíncronos de forma síncrona; pode ser facilmente usada de maneira inadequada para causar conflitos.
Erik Philips
1
Obrigado. 2 perguntas de acompanhamento: 1) Você pode dar um exemplo de algo que o método assíncrono deseja evitar que cause um impasse e 2) os impasses nesse contexto geralmente dependem do tempo? Se funcionar na prática, ainda posso ter um impasse dependente do tempo à espreita no meu código?
Bob.at.Indigo.Health
@ Bob.at.SBS Eu recomendaria fazer perguntas usando o botão Perguntar no canto superior direito. Você pode incluir um link para esta pergunta ou responder na sua pergunta como referência.
Erik Philips
1
@ Bob.at ... o código fornecido por Erik funciona perfeitamente em Asp. net mvc5 e EF6, mas não quando tentei qualquer uma das outras soluções (ConfigureAwait (false) .GetAwaiter (). GetResult () ou .result) que trava completamente meu aplicativo Web
LeonardoX
151

O async Main agora faz parte do C # 7.2 e pode ser ativado nas configurações avançadas de construção do projeto.

Para C # <7.2, a maneira correta é:

static void Main(string[] args)
{
   MainAsync().GetAwaiter().GetResult();
}


static async Task MainAsync()
{
   /*await stuff here*/
}

Você verá isso usado em muitas documentações da Microsoft, por exemplo: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- tópicos-assinaturas

Lee Smith
fonte
11
Não faço ideia por que alguém votou contra. Isso funcionou muito bem para mim. Sem essa correção, eu teria que propagar ASYCH EM TODA PARTE.
Prisioneiro ZERO
11
Por que isso é melhor do que MainAsync().Wait()?
esmagar
8
Concordo. Você só precisa de MainAsync (). Wait () em vez de tudo isso.
Hajja
8
@crush Eu estava descrevendo como isso pode evitar alguns impasses. Em algumas situações, chamar .Wait () de um thread da interface do usuário ou asp.net causa um conflito. deadlocks assíncronos
David
6
@ClintB: Você absolutamente não deve fazer isso no ASP.NET Core. Os aplicativos da Web são particularmente vulneráveis ​​à falta de encadeamento e, toda vez que você faz isso, está puxando um encadeamento do pool que, de outra forma, seria usado para atender a uma solicitação. É menos problemático para aplicativos de desktop / dispositivos móveis porque eles são tradicionalmente um único usuário.
22817 Chris Pratt
52
public async Task<string> StartMyTask()
{
    await Foo()
    // code to execute once foo is done
}

static void Main()
{
     var myTask = StartMyTask(); // call your method which will return control once it hits await
     // now you can continue executing code here
     string result = myTask.Result; // wait for the task to complete to continue
     // use result

}

Você lê a palavra-chave 'aguardar' como "inicie esta tarefa de longa execução e retorne o controle ao método de chamada". Depois que a tarefa de execução longa é concluída, ela executa o código após ela. O código após a espera é semelhante ao que costumava ser métodos de retorno de chamada. A grande diferença é que o fluxo lógico não é interrompido, o que facilita muito a escrita e a leitura.

Despertar
fonte
15
Waitquebra exceções e tem a possibilidade de um impasse.
Stephen Cleary
Eu pensei que se você chamasse um método assíncrono sem usar await, ele seria executado de forma síncrona. Pelo menos isso funciona para mim (sem ligar myTask.Wait). Na verdade, recebi uma exceção quando tentei ligar myTask.RunSynchronously()porque já havia sido executado!
temor
2
Eu gosto desta resposta. Bons comentários para edição, pequenos e elegantes. Obrigado por contribuir! Eu ainda está aprendendo a simultaneidade, por isso tudo ajuda :)
kayleeFrye_onDeck
2
Essa resposta ainda deve funcionar a partir de hoje? Eu apenas tentei em um projeto MVC Razor e o aplicativo fica parado ao acessar .Result.
Gone Coding
8
@TrueBlueAussie Esse é o conflito do contexto de sincronização. Seu código assíncrono retorna ao contexto de sincronização, mas está sendo bloqueado pela Resultchamada no momento, para que nunca chegue lá. E Resultnunca acaba, porque está esperando alguém que está esperando o Resultfim, basicamente: D
Luaan
40

Não tenho 100% de certeza, mas acredito que a técnica descrita neste blog deve funcionar em várias circunstâncias:

Assim, você pode usar task.GetAwaiter().GetResult()se desejar invocar diretamente essa lógica de propagação.

NStuke
fonte
6
A solução A da resposta de Stephen Cleary acima usa esse método. Consulte a fonte WaitAndUnwrapException .
orad 29/03/17
você precisa usar GetResult () se a função que você está chamando for nula ou tarefa? Quero dizer, se você não quiser obter quaisquer resultados de volta
batmaci
Sim, caso contrário, ele não será bloqueado até a conclusão da tarefa. Alternativamente, em vez de chamar GetAwaiter () GetResult () você pode chamar .Wait ().
NStuke
1
Essa é a parte "muitas circunstâncias". Depende do modelo geral de encadeamento e do que os outros encadeamentos estão fazendo para determinar se há um risco de conflito ou não.
NStuke 29/03/18
GetAwaiter (). GetResult () ainda pode causar conflitos. Apenas envolve a exceção em uma mais sensata.
nawfal
25

No entanto, existe uma boa solução que funciona em (quase: veja comentários) todas as situações: uma bomba de mensagens ad-hoc (SynchronizationContext).

O encadeamento de chamada será bloqueado conforme o esperado, garantindo ainda que todas as continuações chamadas da função assíncrona não entrem em conflito, pois serão empacotadas para o SynchronizationContext (bomba de mensagens) ad-hoc em execução no encadeamento de chamada.

O código do auxiliar de bomba de mensagem ad-hoc:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Threading
{
    /// <summary>Provides a pump that supports running asynchronous methods on the current thread.</summary>
    public static class AsyncPump
    {
        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Action asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(true);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function
                syncCtx.OperationStarted();
                asyncMethod();
                syncCtx.OperationCompleted();

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Func<Task> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static T Run<T>(Func<Task<T>> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                return t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Provides a SynchronizationContext that's single-threaded.</summary>
        private sealed class SingleThreadSynchronizationContext : SynchronizationContext
        {
            /// <summary>The queue of work items.</summary>
            private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> m_queue =
                new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
            /// <summary>The processing thread.</summary>
            private readonly Thread m_thread = Thread.CurrentThread;
            /// <summary>The number of outstanding operations.</summary>
            private int m_operationCount = 0;
            /// <summary>Whether to track operations m_operationCount.</summary>
            private readonly bool m_trackOperations;

            /// <summary>Initializes the context.</summary>
            /// <param name="trackOperations">Whether to track operation count.</param>
            internal SingleThreadSynchronizationContext(bool trackOperations)
            {
                m_trackOperations = trackOperations;
            }

            /// <summary>Dispatches an asynchronous message to the synchronization context.</summary>
            /// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param>
            /// <param name="state">The object passed to the delegate.</param>
            public override void Post(SendOrPostCallback d, object state)
            {
                if (d == null) throw new ArgumentNullException("d");
                m_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
            }

            /// <summary>Not supported.</summary>
            public override void Send(SendOrPostCallback d, object state)
            {
                throw new NotSupportedException("Synchronously sending is not supported.");
            }

            /// <summary>Runs an loop to process all queued work items.</summary>
            public void RunOnCurrentThread()
            {
                foreach (var workItem in m_queue.GetConsumingEnumerable())
                    workItem.Key(workItem.Value);
            }

            /// <summary>Notifies the context that no more work will arrive.</summary>
            public void Complete() { m_queue.CompleteAdding(); }

            /// <summary>Invoked when an async operation is started.</summary>
            public override void OperationStarted()
            {
                if (m_trackOperations)
                    Interlocked.Increment(ref m_operationCount);
            }

            /// <summary>Invoked when an async operation is completed.</summary>
            public override void OperationCompleted()
            {
                if (m_trackOperations &&
                    Interlocked.Decrement(ref m_operationCount) == 0)
                    Complete();
            }
        }
    }
}

Uso:

AsyncPump.Run(() => FooAsync(...));

Uma descrição mais detalhada da bomba assíncrona está disponível aqui .

Robert J
fonte
Contexto de exceção e AsyncPump stackoverflow.com/questions/23161693/…
PreguntonCojoneroCabrón
Isso não funciona em um cenário do Asp.net, pois você pode perder aleatoriamente o HttpContext.Current.
Josh Mouch
12

Para quem mais prestar atenção a esta pergunta ...

Se você olhar, Microsoft.VisualStudio.Services.WebApihá uma classe chamada TaskExtensions. Dentro dessa classe, você verá o método de extensão estáticaTask.SyncResult() , que bloqueia totalmente o thread até que a tarefa retorne.

Internamente, ele chama, o task.GetAwaiter().GetResult()que é bastante simples, mas está sobrecarregado para funcionar em qualquer asyncmétodo que retorne Task, Task<T>ouTask<HttpResponseMessage> ... açúcar sintático, bebê ... papai tem um gosto por doces.

Parece que ...GetAwaiter().GetResult()é a maneira oficial da MS de executar código assíncrono em um contexto de bloqueio. Parece funcionar muito bem no meu caso de uso.

jrypkahauer
fonte
3
Você me teve como "totalmente apenas a alguns quarteirões".
Dawood ibn Kareem
9
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();

Ou use isto:

var result=result.GetAwaiter().GetResult().AccessToken
rajesh A
fonte
6

Você pode chamar qualquer método assíncrono a partir de código síncrono, ou seja, até precisar awaitdeles, caso em que eles também deverão ser marcados async.

Como muitas pessoas sugerem aqui, você pode chamar Wait () ou Result na tarefa resultante no seu método síncrono, mas então você acaba com uma chamada de bloqueio nesse método, o que meio que derrota o propósito da assíncrona.

Se você realmente não pode criar seu método asynce não deseja bloquear o método síncrono, precisará usar um método de retorno de chamada passando-o como parâmetro para o método ContinueWith na tarefa.

base2
fonte
5
Então isso não seria chamar o método de forma síncrona agora, seria?
Jeff Mercado
2
Pelo que entendi, a pergunta era: você pode chamar um método assíncrono de um método não assíncrono. Isso não implica ter que chamar o método assíncrono de maneira bloqueadora.
base2 18/02/12
Desculpe, seu "eles também precisam ser marcados async" desviou minha atenção do que você estava realmente dizendo.
Jeff Mercado
Se eu realmente não me importo com a assíncrona, não há problema em chamá-lo dessa maneira (e quanto à possibilidade de conflitos em exceções agrupadas que Stephen Cleary continua incomodando?) Eu tenho alguns métodos de teste (que devem ser executados de forma síncrona) que testa métodos assíncronos. Devo aguardar o resultado antes de continuar, para poder testar o resultado do método assíncrono.
temor
6

Eu sei que estou tão atrasada. Mas no caso de alguém como eu querer resolver isso de maneira organizada, fácil e sem depender de outra biblioteca.

Encontrei o seguinte trecho de código de Ryan

public static class AsyncHelpers
{
    private static readonly TaskFactory taskFactory = new
        TaskFactory(CancellationToken.None,
            TaskCreationOptions.None,
            TaskContinuationOptions.None,
            TaskScheduler.Default);

    /// <summary>
    /// Executes an async Task method which has a void return value synchronously
    /// USAGE: AsyncUtil.RunSync(() => AsyncMethod());
    /// </summary>
    /// <param name="task">Task method to execute</param>
    public static void RunSync(Func<Task> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();

    /// <summary>
    /// Executes an async Task<T> method which has a T return type synchronously
    /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod<T>());
    /// </summary>
    /// <typeparam name="TResult">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static TResult RunSync<TResult>(Func<Task<TResult>> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();
}

então você pode chamar assim

var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());
Wahid Bitar
fonte
6
Isso parece exatamente como o acima resposta estou faltando alguma coisa
inlokesh
2

Depois de horas tentando métodos diferentes, com mais ou menos sucesso, foi com isso que acabei. Ele não termina em um impasse ao obter resultado e também recebe e lança a exceção original e não a finalizada.

private ReturnType RunSync()
{
  var task = Task.Run(async () => await myMethodAsync(agency));
  if (task.IsFaulted && task.Exception != null)
  {
    throw task.Exception;
  }

  return task.Result;
}
Jiří Herník
fonte
1
Funciona com a tarefa de retorno.GetAwaiter (). GetResult ();
Por G
sim, mas e a exceção original?
Jiří Herník 26/03
.Result Eu acho que é o mesmo basicamente que .GetAwaiter (). GetResult ()
Por G
-2

Pode ser chamado de um novo encadeamento (NÃO do conjunto de encadeamentos!):

public static class SomeHelperClass
{ 
       public static T Result<T>(Func<T> func)
        {
            return Task.Factory.StartNew<T>(
                  () => func()
                , TaskCreationOptions.LongRunning
                ).Result;
        }
}
...
content = SomeHelperClass.Result<string>(
  () => response.Content.ReadAsStringAsync().Result
  );
Garm
fonte
-3

Esses métodos assíncronos do Windows têm um pequeno método bacana chamado AsTask (). Você pode usar isso para que o método retorne como uma tarefa, para que você possa chamar manualmente Wait ().

Por exemplo, em um aplicativo Windows Phone 8 Silverlight, você pode fazer o seguinte:

private void DeleteSynchronous(string path)
{
    StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    Task t = localFolder.DeleteAsync(StorageDeleteOption.PermanentDelete).AsTask();
    t.Wait();
}

private void FunctionThatNeedsToBeSynchronous()
{
    // Do some work here
    // ....

    // Delete something in storage synchronously
    DeleteSynchronous("pathGoesHere");

    // Do other work here 
    // .....
}

Espero que isto ajude!

Foxy
fonte
-4

Se você deseja executá-lo Sync

MethodAsync().RunSynchronously()
smj
fonte
3
Este método é destinado para iniciar tarefas frias. Normalmente, os métodos assíncronos retornam uma tarefa ativa, ou seja, uma tarefa que já foi iniciada. chamar RunSynchronously()uma tarefa quente resulta em um InvalidOperationException. Experimente com este código:Task.Run(() => {}).RunSynchronously();
Theodor Zoulias
-5
   //Example from non UI thread -    
   private void SaveAssetAsDraft()
    {
        SaveAssetDataAsDraft();
    }
    private async Task<bool> SaveAssetDataAsDraft()
    {
       var id = await _assetServiceManager.SavePendingAssetAsDraft();
       return true;   
    }
   //UI Thread - 
   var result = Task.Run(() => SaveAssetDataAsDraft().Result).Result;
Arvind Kumar Chaodhary
fonte
2
Gere impasse. Melhor excluir a resposta.
PreguntonCojoneroCabrón
Task.Run (() => SaveAssetDataAsDraft ()). Result; - não gera conflito
Anubis