Domanda

Ho un public async void Foo() Metodo che voglio chiamare dal metodo sincrono. Finora tutto ciò che ho visto dalla documentazione di MSDN è chiamare i metodi Async tramite metodi Async, ma il mio intero programma non è creato con metodi Async.

È anche possibile?

Ecco un esempio di chiamare questi metodi da un metodo asincrono: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx

Ora sto cercando di chiamare questi metodi asincrici dai metodi di sincronizzazione.

È stato utile?

Soluzione

La programmazione asincrona "cresce" attraverso la base di codice. È stato Rispetto a un virus zombi. La soluzione migliore è permettergli di crescere, ma a volte non è possibile.

Ho scritto alcuni tipi nel mio Nito.Asyncex Libreria per trattare con una base di codice parzialmente asincrona. Tuttavia, non c'è soluzione che funzioni in ogni situazione.

Soluzione A.

Se hai un semplice metodo asincrono che non è necessario sincronizzare nel suo contesto, allora puoi usare Task.WaitAndUnwrapException:

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

Tu fai non voglio usare Task.Wait o Task.Result Perché avvolgono le eccezioni in AggregateException.

Questa soluzione è appropriata solo se MyAsyncMethod non si sincronizza al suo contesto. In altre parole, ogni await in MyAsyncMethod dovrebbe finire con ConfigureAwait(false). Ciò significa che non può aggiornare alcun elemento dell'interfaccia utente o accedere al contesto della richiesta ASP.NET.

Soluzione b

Se MyAsyncMethod Deve sincronizzarsi al suo contesto, allora potresti essere in grado di utilizzare AsyncContext.RunTask Per fornire un contesto nidificato:

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

*Aggiornamento 14/04/2014: nelle versioni più recenti della biblioteca l'API è la seguente:

var result = AsyncContext.Run(MyAsyncMethod);

(Va bene usare Task.Result In questo esempio perché RunTask si propagerà Task eccezioni).

Il motivo per cui potresti aver bisogno AsyncContext.RunTask invece di Task.WaitAndUnwrapException è a causa di una possibilità piuttosto sottile che si verifica su Winforms/WPF/SL/ASP.NET:

  1. Un metodo sincrono chiama un metodo asincrone, ottenendo un file Task.
  2. Il metodo sincrono fa un'attesa bloccante su Task.
  3. Il async usi del metodo await senza ConfigureAwait.
  4. Il Task non può completare in questa situazione perché completa solo quando il async Il metodo è finito; il async Il metodo non può completare perché sta tentando di programmare la sua continuazione a SynchronizationContext, e Winforms/WPF/SL/ASP.NET non consentirà l'esecuzione della continuazione perché il metodo sincrono è già in esecuzione in quel contesto.

Questo è uno dei motivi per cui è una buona idea da usare ConfigureAwait(false) all'interno di ogni async metodo il più possibile.

Soluzione c

AsyncContext.RunTask Non funzionerà in ogni scenario. Ad esempio, se il async Il metodo attende qualcosa che richiede un evento dell'interfaccia utente da completare, quindi avrai il punto anche con il contesto nidificato. In tal caso, potresti iniziare il async Metodo sul pool di thread:

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

Tuttavia, questa soluzione richiede un file MyAsyncMethod Funzionerà nel contesto del pool di thread. Quindi non può aggiornare gli elementi dell'interfaccia utente o accedere al contesto della richiesta ASP.NET. E in tal caso, puoi anche aggiungere ConfigureAwait(false) al suo await dichiarazioni e usa la soluzione A.

AGGIORNAMENTO, 2019-05-01: Le attuali "pratiche di meno attesa" sono in un Articolo MSDN qui.

Altri suggerimenti

L'aggiunta di una soluzione che ha finalmente risolto il mio problema, si spera che risparmia il tempo di qualcuno.

Innanzitutto leggi un paio di articoli di Stephen Cleary:

Dalle "due migliori pratiche" in "non bloccare il codice asincrico", il primo non ha funzionato per me e il secondo non era applicabile (in sostanza se posso usare await, Io faccio!).

Quindi ecco la mia soluzione alternativa: avvolgi la chiamata all'interno di Task.Run<>(async () => await FunctionAsync()); E si spera che no Deadlock più.

Ecco il mio codice:

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;
    }
}

Microsoft ha creato una classe Asynchelper (interna) per eseguire Async come sincronizzazione. La fonte sembra:

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();
    }
}

Le classi di base di Microsoft.aspnet.Identity hanno solo metodi asincrici e per chiamarli come sincronizzazione ci sono classi con metodi di estensione che sembrano (utilizzo di esempio):

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));
}

Per coloro che sono preoccupati per i termini di licenza del codice, ecco un collegamento a un codice molto simile (aggiunge solo supporto per la cultura sul thread) che ha commenti per indicare che è autorizzato a Microsoft. https://github.com/aspnet/aspne identity/blob/master/src/microsoft.aspnet.identity.core/asynchelper.cs

Async Main fa ora parte di C# 7.2 e può essere abilitato nelle impostazioni di build avanzate di progetti.

Per C# <7.2, il modo corretto è:

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


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

Vedrai questo utilizzato in molta documentazione Microsoft, ad esempio:https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use-topics-subsscriptions

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

}

Leggi la parola chiave "Aspetta" come "Avvia questa attività a lungo termine, quindi restituisci il controllo al metodo chiamante". Una volta eseguita l'attività di lunga data, quindi esegue il codice dopo di esso. Il codice dopo l'attesa è simile a quelli che erano metodi di callback. La grande differenza è che il flusso logico non viene interrotto, il che rende molto più facile scrivere e leggere.

Non sono sicuro al 100%, ma credo che la tecnica descritta in Questo blog dovrebbe funzionare in molte circostanze:

Puoi quindi usare task.GetAwaiter().GetResult() Se si desidera invocare direttamente questa logica di propagazione.

La risposta più accettata non è del tutto corretta. Esiste una soluzione che funziona in ogni situazione: una pompa di messaggi ad hoc (SynchronizationContext).

Il thread chiamante verrà bloccato come previsto, pur garantendo che tutte le continue chiamate dalla funzione asincrima non bloccano in quanto verranno riuniti alla sincronizzazione ad hoc (pompa di messaggi) in esecuzione sul thread chiamante.

Il codice del supporto per pompa del messaggio 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(...));

È disponibile una descrizione più dettagliata della pompa asincrimale qui.

A chiunque prestasse più attenzione a questa domanda ...

Se guardi dentro Microsoft.VisualStudio.Services.WebApi C'è una classe chiamata TaskExtensions. All'interno di quella classe vedrai il metodo di estensione statica Task.SyncResult(), che piace totalmente blocca il thread fino a quando l'attività non ritorna.

Internamente chiama task.GetAwaiter().GetResult() Il che è piuttosto semplice, tuttavia è sovraccarico di lavorare su qualsiasi async Metodo che ritorna Task, Task<T> o Task<HttpResponseMessage>... zucchero sintattico, piccola ... papà ha un debole per i dolci.

Sembra ...GetAwaiter().GetResult() è il modo di MS-official di eseguire il codice Async in un contesto di blocco. Sembra funzionare molto bene per il mio caso d'uso.

var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

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

O usa questo:

var result=result.GetAwaiter().GetResult().AccessToken

Puoi chiamare qualsiasi metodo asincrono dal codice sincrono, cioè fino a quando non è necessario await su di loro, nel qual caso devono essere contrassegnati async anche.

Come molte persone stanno suggerendo qui, potresti chiamare Wait () o derivare dall'attività risultante nel tuo metodo sincrono, ma poi si finisce con una chiamata di blocco in quel metodo, che in qualche modo sconfigge lo scopo dell'asincronizzazione.

Non puoi davvero creare il tuo metodo async E non vuoi bloccare il metodo sincrono, quindi dovrai utilizzare un metodo di callback passandolo come parametro al metodo Continuewith sull'attività.

So di essere così tardi. Ma nel caso in cui qualcuno come me volesse risolverlo in modo pulito e facile e senza dipendere da un'altra biblioteca.

Ho trovato quanto segue pezzo di codice da 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();
}

allora puoi chiamarlo così

var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());

Se vuoi eseguirlo sincronizza

MethodAsync().RunSynchronously()

Quei metodi di asincroni di Windows hanno un piccolo metodo elegante chiamato Astask (). Puoi usarlo per avere il metodo restituire da solo come attività in modo da poter chiamare manualmente Wait () su di esso.

Ad esempio, su un'applicazione Silverlight di Windows Phone 8, puoi fare quanto segue:

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 
    // .....
}

Spero che sia di aiuto!

   //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;
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top