it-swarm.com.ru

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

У меня есть метод 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;
}
205
Catalin

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

string code = GenerateCodeAsync().Result;

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

252
Heinzi

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

string code = GenerateCodeAsync().GetAwaiter().GetResult();
51
Diego Torres

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

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" ;
    }
28
Faiyaz

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

Это возможно с 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. Вы должны использовать это. Для более подробной информации, проверьте этот ответ .

20
noseratio

У 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();
17
Vitaliy Markitanov

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

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

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

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

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

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

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