Вызов асинхронного метода синхронно

У меня есть async метод:

public async Task<string> GenerateCodeAsync()
{
    string code = await GenerateCodeService.GenerateCodeAsync();
    return code;
}

Мне нужно вызвать этот метод из синхронного метода.

Как я могу сделать это, не дублируя GenerateCodeAsync метод для того, чтобы это работало синхронно?

Обновить

Пока не найдено разумного решения.

Тем не менее, я вижу, что HttpClient уже реализует этот шаблон

using (HttpClient client = new HttpClient())
{
    // async
    HttpResponseMessage responseAsync = await client.GetAsync(url);

    // sync
    HttpResponseMessage responseSync = client.GetAsync(url).Result;
}

15 ответов

Вы можете получить доступ к Result свойство задачи, которое заставит ваш поток блокироваться, пока результат не станет доступен:

string code = GenerateCodeAsync().Result;

Примечание. В некоторых случаях это может привести к тупику. Result блокирует основной поток, тем самым предотвращая выполнение оставшейся части асинхронного кода. У вас есть следующие варианты, чтобы убедиться, что этого не произойдет:

  • добавлять .ConfigureAwait(false) в ваш метод библиотеки или
  • явным образом выполните асинхронный метод в потоке пула потоков и дождитесь его завершения:

    string code = Task.Run(GenerateCodeAsync).Result;
    

Вы должны получить официанта (GetAwaiter()) и завершите ожидание завершения асинхронной задачи (GetResult()).

string code = GenerateCodeAsync().GetAwaiter().GetResult();

Вы должны быть в состоянии сделать это с помощью делегатов, лямбда-выражения

private void button2_Click(object sender, EventArgs e)
    {

        label1.Text = "waiting....";

        Task<string> sCode = Task.Run(async () =>
        {
            string msg =await GenerateCodeAsync();
            return msg;
        });

        label1.Text += sCode.Result;

    }

    private Task<string> GenerateCodeAsync()
    {
        return Task.Run<string>(() => GenerateCode());
    }

    private string GenerateCode()
    {
        Thread.Sleep(2000);
        return "I m back" ;
    }

У Microsoft Identity есть методы расширения, которые синхронно вызывают асинхронные методы. Например, есть метод GenerateUserIdentityAsync() и равный CreateIdentity()

Если вы посмотрите на UserManagerExtensions.CreateIdentity(), это будет выглядеть так:

 public static ClaimsIdentity CreateIdentity<TUser, TKey>(this UserManager<TUser, TKey> manager, TUser user,
        string authenticationType)
        where TKey : IEquatable<TKey>
        where TUser : class, IUser<TKey>
    {
        if (manager == null)
        {
            throw new ArgumentNullException("manager");
        }
        return AsyncHelper.RunSync(() => manager.CreateIdentityAsync(user, authenticationType));
    }

Теперь давайте посмотрим, что делает AsyncHelper.RunSync

  public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        var cultureUi = CultureInfo.CurrentUICulture;
        var culture = CultureInfo.CurrentCulture;
        return _myTaskFactory.StartNew(() =>
        {
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = cultureUi;
            return func();
        }).Unwrap().GetAwaiter().GetResult();
    }

Итак, это ваша оболочка для асинхронного метода. И, пожалуйста, не читайте данные из Result - это потенциально заблокирует ваш код в ASP.

Есть и другой способ - который мне кажется подозрительным, но вы тоже можете это рассмотреть

  Result r = null;

            YourAsyncMethod()
                .ContinueWith(t =>
                {
                    r = t.Result;
                })
                .Wait();

Для предотвращения тупиков я всегда стараюсь использовать Task.Run() когда мне нужно синхронно вызывать асинхронный метод, который упоминает @Heinzi.

Однако метод должен быть модифицирован, если асинхронный метод использует параметры. Например Task.Run(GenerateCodeAsync("test")).Result выдает ошибку:

Аргумент 1: невозможно преобразовать из 'System.Threading.Tasks.Task<string>к "System.Action"

Это можно назвать так:

string code = Task.Run(() => GenerateCodeAsync("test")).Result;

Мне нужно вызвать этот метод из синхронного метода.

Это возможно с GenerateCodeAsync().Result или же GenerateCodeAsync().Wait(), как предполагает другой ответ. Это заблокирует текущий поток до GenerateCodeAsync завершено.

Тем не менее, ваш вопрос помечен asp.net, и вы также оставили комментарий:

Я надеялся на более простое решение, полагая, что asp.net справится с этим гораздо проще, чем написание стольких строк кода

Я хочу сказать, что вы не должны блокировать асинхронный метод в ASP.NET. Это уменьшит масштабируемость вашего веб-приложения и может создать тупик (когда await продолжение внутри GenerateCodeAsync размещен в AspNetSynchronizationContext). С помощью Task.Run(...).Result разгрузка чего-либо в поток пула, а затем его блокирование еще больше повредит масштабируемости, поскольку для обработки данного HTTP-запроса потребуется еще +1 поток.

ASP.NET имеет встроенную поддержку асинхронных методов, либо через асинхронные контроллеры (в ASP.NET MVC и Web API), либо напрямую через AsyncManager а также PageAsyncTask в классическом ASP.NET. Вы должны использовать это. Для более подробной информации, проверьте этот ответ.

Большинство ответов в этой теме либо сложны, либо приводят к тупиковой ситуации.

Следующий метод прост, и он позволит избежать тупика, потому что мы ждем завершения задачи и только потом получаем ее результат.

var task = Task.Run(() => GenerateCodeAsync()); 
task.Wait();
string code = task.Result;

Кроме того, вот ссылка на статью MSDN, в которой говорится о том же самом: https://blogs.msdn.microsoft.com/jpsanders/2017/08/28/asp-net-do-not-use-task-result-in-main-context/

Я использую такой подход:

    private string RunSync()
    {
        var task = Task.Run(async () => await GenerateCodeService.GenerateCodeAsync());
        if (task.IsFaulted && task.Exception != null)
        {
            throw task.Exception;
        }

        return task.Result;
    }

Другой способ может быть, если вы хотите дождаться завершения задачи:

var t = GenerateCodeService.GenerateCodeAsync();
Task.WhenAll(t);
string code = t.Result;

Я предпочитаю неблокирующий подход:

            Dim aw1=GenerateCodeAsync().GetAwaiter()
            While Not aw1.IsCompleted
                Application.DoEvents()
            End While

Вы можете использовать библиотеку генератора методов синхронизации (nuget) для создания синхронизированной версии этого кода.

Используйте его следующим образом:

      [Zomp.SyncMethodGenerator.CreateSyncVersion]
public async Task<string> GenerateCodeAsync()
{
    string code = await GenerateCodeService.GenerateCodeAsync();
    return code;
}

Что будет генерироватьGenerateCodeметод, который вы можете вызывать синхронно.

Источник, который будет сгенерирован:

      public string GenerateCode()
{
    string code = GenerateCodeService.GenerateCode();
    return code;
}

Как насчет некоторых методов расширения, которые асинхронно ожидают завершения асинхронной операции, а затем устанавливают ManualResetEvent для указания завершения.

ПРИМЕЧАНИЕ. Вы можете использовать Task.Run(), однако методы расширения представляют собой более чистый интерфейс для выражения того, что вы действительно хотите.

Тесты, показывающие, как использовать расширения:

          [TestClass]
    public class TaskExtensionsTests
    {
        [TestMethod]
        public void AsynchronousOperationWithNoResult()
        {
            SampleAsynchronousOperationWithNoResult().AwaitResult();
        }

        [TestMethod]
        public void AsynchronousOperationWithResult()
        {
            Assert.AreEqual(3, SampleAsynchronousOperationWithResult(3).AwaitResult());
        }

        [TestMethod]
        [ExpectedException(typeof(Exception))]
        public void AsynchronousOperationWithNoResultThrows()
        {
            SampleAsynchronousOperationWithNoResultThrows().AwaitResult();
        }

        [TestMethod]
        [ExpectedException(typeof(Exception))]
        public void AsynchronousOperationWithResultThrows()
        {
            SampleAsynchronousOperationWithResultThrows(3).AwaitResult();
        }

        private static async Task SampleAsynchronousOperationWithNoResult()
        {
            await Task.Yield();
        }

        private static async Task<T> SampleAsynchronousOperationWithResult<T>(T result)
        {
            await Task.Yield();
            return result;
        }

        private static async Task SampleAsynchronousOperationWithNoResultThrows()
        {
            await Task.Yield();
            throw new Exception();
        }

        private static async Task<T> SampleAsynchronousOperationWithResultThrows<T>(T result)
        {
            await Task.Yield();
            throw new Exception();
        }

        [TestMethod]
        public void AsynchronousValueOperationWithNoResult()
        {
            SampleAsynchronousValueOperationWithNoResult().AwaitResult();
        }

        [TestMethod]
        public void AsynchronousValueOperationWithResult()
        {
            Assert.AreEqual(3, SampleAsynchronousValueOperationWithResult(3).AwaitResult());
        }

        [TestMethod]
        [ExpectedException(typeof(Exception))]
        public void AsynchronousValueOperationWithNoResultThrows()
        {
            SampleAsynchronousValueOperationWithNoResultThrows().AwaitResult();
        }

        [TestMethod]
        [ExpectedException(typeof(Exception))]
        public void AsynchronousValueOperationWithResultThrows()
        {
            SampleAsynchronousValueOperationWithResultThrows(3).AwaitResult();
        }

        private static async ValueTask SampleAsynchronousValueOperationWithNoResult()
        {
            await Task.Yield();
        }

        private static async ValueTask<T> SampleAsynchronousValueOperationWithResult<T>(T result)
        {
            await Task.Yield();
            return result;
        }

        private static async ValueTask SampleAsynchronousValueOperationWithNoResultThrows()
        {
            await Task.Yield();
            throw new Exception();
        }

        private static async ValueTask<T> SampleAsynchronousValueOperationWithResultThrows<T>(T result)
        {
            await Task.Yield();
            throw new Exception();
        }
    }

Расширения

          /// <summary>
    /// Defines extension methods for <see cref="Task"/> and <see cref="ValueTask"/>.
    /// </summary>
    public static class TaskExtensions
    {
        /// <summary>
        /// Synchronously await the results of an asynchronous operation without deadlocking; ignoring cancellation.
        /// </summary>
        /// <param name="task">
        /// The <see cref="Task"/> representing the pending operation.
        /// </param>
        public static void AwaitCompletion(this ValueTask task)
        {
            new SynchronousAwaiter(task, true).GetResult();
        }

        /// <summary>
        /// Synchronously await the results of an asynchronous operation without deadlocking; ignoring cancellation.
        /// </summary>
        /// <param name="task">
        /// The <see cref="Task"/> representing the pending operation.
        /// </param>
        public static void AwaitCompletion(this Task task)
        {
            new SynchronousAwaiter(task, true).GetResult();
        }

        /// <summary>
        /// Synchronously await the results of an asynchronous operation without deadlocking.
        /// </summary>
        /// <param name="task">
        /// The <see cref="Task"/> representing the pending operation.
        /// </param>
        /// <typeparam name="T">
        /// The result type of the operation.
        /// </typeparam>
        /// <returns>
        /// The result of the operation.
        /// </returns>
        public static T AwaitResult<T>(this Task<T> task)
        {
            return new SynchronousAwaiter<T>(task).GetResult();
        }

        /// <summary>
        /// Synchronously await the results of an asynchronous operation without deadlocking.
        /// </summary>
        /// <param name="task">
        /// The <see cref="Task"/> representing the pending operation.
        /// </param>
        public static void AwaitResult(this Task task)
        {
            new SynchronousAwaiter(task).GetResult();
        }

        /// <summary>
        /// Synchronously await the results of an asynchronous operation without deadlocking.
        /// </summary>
        /// <param name="task">
        /// The <see cref="ValueTask"/> representing the pending operation.
        /// </param>
        /// <typeparam name="T">
        /// The result type of the operation.
        /// </typeparam>
        /// <returns>
        /// The result of the operation.
        /// </returns>
        public static T AwaitResult<T>(this ValueTask<T> task)
        {
            return new SynchronousAwaiter<T>(task).GetResult();
        }

        /// <summary>
        /// Synchronously await the results of an asynchronous operation without deadlocking.
        /// </summary>
        /// <param name="task">
        /// The <see cref="ValueTask"/> representing the pending operation.
        /// </param>
        public static void AwaitResult(this ValueTask task)
        {
            new SynchronousAwaiter(task).GetResult();
        }

        /// <summary>
        /// Ignore the <see cref="OperationCanceledException"/> if the operation is cancelled.
        /// </summary>
        /// <param name="task">
        /// The <see cref="Task"/> representing the asynchronous operation whose cancellation is to be ignored.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/> representing the asynchronous operation whose cancellation is ignored.
        /// </returns>
        public static async Task IgnoreCancellationResult(this Task task)
        {
            try
            {
                await task.ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
            }
        }

        /// <summary>
        /// Ignore the <see cref="OperationCanceledException"/> if the operation is cancelled.
        /// </summary>
        /// <param name="task">
        /// The <see cref="ValueTask"/> representing the asynchronous operation whose cancellation is to be ignored.
        /// </param>
        /// <returns>
        /// The <see cref="ValueTask"/> representing the asynchronous operation whose cancellation is ignored.
        /// </returns>
        public static async ValueTask IgnoreCancellationResult(this ValueTask task)
        {
            try
            {
                await task.ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
            }
        }

        /// <summary>
        /// Ignore the results of an asynchronous operation allowing it to run and die silently in the background.
        /// </summary>
        /// <param name="task">
        /// The <see cref="Task"/> representing the asynchronous operation whose results are to be ignored.
        /// </param>
        public static async void IgnoreResult(this Task task)
        {
            try
            {
                await task.ConfigureAwait(false);
            }
            catch
            {
                // ignore exceptions
            }
        }

        /// <summary>
        /// Ignore the results of an asynchronous operation allowing it to run and die silently in the background.
        /// </summary>
        /// <param name="task">
        /// The <see cref="ValueTask"/> representing the asynchronous operation whose results are to be ignored.
        /// </param>
        public static async void IgnoreResult(this ValueTask task)
        {
            try
            {
                await task.ConfigureAwait(false);
            }
            catch
            {
                // ignore exceptions
            }
        }
    }

    /// <summary>
    /// Internal class for waiting for asynchronous operations that have a result.
    /// </summary>
    /// <typeparam name="TResult">
    /// The result type.
    /// </typeparam>
    public class SynchronousAwaiter<TResult>
    {
        /// <summary>
        /// The manual reset event signaling completion.
        /// </summary>
        private readonly ManualResetEvent manualResetEvent;

        /// <summary>
        /// The exception thrown by the asynchronous operation.
        /// </summary>
        private Exception exception;

        /// <summary>
        /// The result of the asynchronous operation.
        /// </summary>
        private TResult result;

        /// <summary>
        /// Initializes a new instance of the <see cref="SynchronousAwaiter{TResult}"/> class.
        /// </summary>
        /// <param name="task">
        /// The task representing an asynchronous operation.
        /// </param>
        public SynchronousAwaiter(Task<TResult> task)
        {
            this.manualResetEvent = new ManualResetEvent(false);
            this.WaitFor(task);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SynchronousAwaiter{TResult}"/> class.
        /// </summary>
        /// <param name="task">
        /// The task representing an asynchronous operation.
        /// </param>
        public SynchronousAwaiter(ValueTask<TResult> task)
        {
            this.manualResetEvent = new ManualResetEvent(false);
            this.WaitFor(task);
        }

        /// <summary>
        /// Gets a value indicating whether the operation is complete.
        /// </summary>
        public bool IsComplete => this.manualResetEvent.WaitOne(0);

        /// <summary>
        /// Synchronously get the result of an asynchronous operation.
        /// </summary>
        /// <returns>
        /// The result of the asynchronous operation.
        /// </returns>
        public TResult GetResult()
        {
            this.manualResetEvent.WaitOne();
            return this.exception != null ? throw this.exception : this.result;
        }

        /// <summary>
        /// Tries to synchronously get the result of an asynchronous operation.
        /// </summary>
        /// <param name="operationResult">
        /// The result of the operation.
        /// </param>
        /// <returns>
        /// The result of the asynchronous operation.
        /// </returns>
        public bool TryGetResult(out TResult operationResult)
        {
            if (this.IsComplete)
            {
                operationResult = this.exception != null ? throw this.exception : this.result;
                return true;
            }

            operationResult = default;
            return false;
        }

        /// <summary>
        /// Background "thread" which waits for the specified asynchronous operation to complete.
        /// </summary>
        /// <param name="task">
        /// The task.
        /// </param>
        private async void WaitFor(Task<TResult> task)
        {
            try
            {
                this.result = await task.ConfigureAwait(false);
            }
            catch (Exception exception)
            {
                this.exception = exception;
            }
            finally
            {
                this.manualResetEvent.Set();
            }
        }

        /// <summary>
        /// Background "thread" which waits for the specified asynchronous operation to complete.
        /// </summary>
        /// <param name="task">
        /// The task.
        /// </param>
        private async void WaitFor(ValueTask<TResult> task)
        {
            try
            {
                this.result = await task.ConfigureAwait(false);
            }
            catch (Exception exception)
            {
                this.exception = exception;
            }
            finally
            {
                this.manualResetEvent.Set();
            }
        }
    }

    /// <summary>
    /// Internal class for  waiting for  asynchronous operations that have no result.
    /// </summary>
    public class SynchronousAwaiter
    {
        /// <summary>
        /// The manual reset event signaling completion.
        /// </summary>
        private readonly ManualResetEvent manualResetEvent = new ManualResetEvent(false);

        /// <summary>
        /// The exception thrown by the asynchronous operation.
        /// </summary>
        private Exception exception;

        /// <summary>
        /// Initializes a new instance of the <see cref="SynchronousAwaiter{TResult}"/> class.
        /// </summary>
        /// <param name="task">
        /// The task representing an asynchronous operation.
        /// </param>
        /// <param name="ignoreCancellation">
        /// Indicates whether to ignore cancellation. Default is false.
        /// </param>
        public SynchronousAwaiter(Task task, bool ignoreCancellation = false)
        {
            this.manualResetEvent = new ManualResetEvent(false);
            this.WaitFor(task, ignoreCancellation);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SynchronousAwaiter{TResult}"/> class.
        /// </summary>
        /// <param name="task">
        /// The task representing an asynchronous operation.
        /// </param>
        /// <param name="ignoreCancellation">
        /// Indicates whether to ignore cancellation. Default is false.
        /// </param>
        public SynchronousAwaiter(ValueTask task, bool ignoreCancellation = false)
        {
            this.manualResetEvent = new ManualResetEvent(false);
            this.WaitFor(task, ignoreCancellation);
        }

        /// <summary>
        /// Gets a value indicating whether the operation is complete.
        /// </summary>
        public bool IsComplete => this.manualResetEvent.WaitOne(0);

        /// <summary>
        /// Synchronously get the result of an asynchronous operation.
        /// </summary>
        public void GetResult()
        {
            this.manualResetEvent.WaitOne();
            if (this.exception != null)
            {
                throw this.exception;
            }
        }

        /// <summary>
        /// Background "thread" which waits for the specified asynchronous operation to complete.
        /// </summary>
        /// <param name="task">
        /// The task.
        /// </param>
        /// <param name="ignoreCancellation">
        /// Indicates whether to ignore cancellation. Default is false.
        /// </param>
        private async void WaitFor(Task task, bool ignoreCancellation)
        {
            try
            {
                await task.ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
            }
            catch (Exception exception)
            {
                this.exception = exception;
            }
            finally
            {
                this.manualResetEvent.Set();
            }
        }

        /// <summary>
        /// Background "thread" which waits for the specified asynchronous operation to complete.
        /// </summary>
        /// <param name="task">
        ///     The task.
        /// </param>
        /// <param name="ignoreCancellation">
        /// Indicates whether to ignore cancellation. Default is false.
        /// </param>
        private async void WaitFor(ValueTask task, bool ignoreCancellation)
        {
            try
            {
                await task.ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
            }
            catch (Exception exception)
            {
                this.exception = exception;
            }
            finally
            {
                this.manualResetEvent.Set();
            }
        }
    }
}

РЕДАКТИРОВАТЬ:

Задача имеет метод Wait, Task.Wait(), который ожидает разрешения "обещания", а затем продолжает выполнение, таким образом делая его синхронным. пример:


async Task<String> MyAsyncMethod() { ... }

String mySyncMethod() {

    return MyAsyncMethod().Wait();
}

Если у вас есть асинхронный метод RefreshList, вы можете вызвать этот асинхронный метод из неасинхронного метода, как показано ниже.

Task.Run(async () => { await RefreshList(); });

OP упоминает, поэтому я просто опубликую это на случай, если это поможет кому-то конкретно.

.NET 5.0 добавил синхронный Send способ HttpClient. https://github.com/dotnet/runtime/pull/34948

Поэтому вы можете использовать это вместо SendAsync. Например

      public string GetValue()
{
    var client = new HttpClient();
            
    var webRequest = new HttpRequestMessage(HttpMethod.Post, "http://your-api.com")
    {
        Content = new StringContent("{ 'some': 'value' }", Encoding.UTF8, "application/json")
    };

    var response = client.Send(webRequest);

    using var reader = new StreamReader(response.Content.ReadAsStream());
            
    return reader.ReadToEnd();
}

Этот код - просто упрощенный пример, он не готов к производству.

Другие вопросы по тегам