Вызов синхронного асинхронного метода
У меня есть метод 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;
}
Ответы
Ответ 1
Вы можете получить доступ к свойству Result
задачи, что приведет к блокировке потока до получения результата:
string code = GenerateCodeAsync().Result;
Примечание. В некоторых случаях это может привести к тупику: ваш вызов Result
блокирует основной поток, тем самым предотвращая выполнение оставшейся части асинхронного кода. У вас есть следующие опции, чтобы убедиться, что этого не происходит:
Ответ 2
Вы должны получить awaiter (GetAwaiter()
) и закончить ожидание завершения асинхронной задачи (GetResult()
).
string code = GenerateCodeAsync().GetAwaiter().GetResult();
Ответ 3
Вы должны сделать это, используя делегаты, lambda expression
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" ;
}
Ответ 4
Мне нужно вызвать этот метод синхронно.
Это возможно с помощью GenerateCodeAsync().Result
или GenerateCodeAsync().Wait()
, как показывает другой ответ. Это блокирует текущий поток до тех пор, пока GenerateCodeAsync
не завершится.
Однако ваш вопрос помечен asp.net, и вы также оставили комментарий:
Я надеялся на более простое решение, думая, что обработано asp.net это намного проще, чем писать так много строк кода
Моя точка зрения: вы не должны блокировать асинхронный метод в ASP.NET. Это уменьшит масштабируемость вашего веб-приложения и может создать тупик (когда продолжение await
внутри GenerateCodeAsync
отправлено на AspNetSynchronizationContext
). Используя Task.Run(...).Result
, чтобы выгрузить что-то в поток пула, а затем блок еще больше повредит масштабируемость, поскольку он обрабатывает +1 больше потока для обработки заданного HTTP-запроса.
ASP.NET имеет встроенную поддержку асинхронных методов либо через асинхронные контроллеры (в ASP.NET MVC и Web API), либо напрямую через AsyncManager
и PageAsyncTask
в классическом ASP.NET. Вы должны использовать его. Подробнее см. этот ответ.
Ответ 5
У 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();
}
Итак, это ваша оболочка для метода async.
И, пожалуйста, не читайте данные из Result - это потенциально блокирует ваш код в ASP.
Есть и другой способ, который для меня подозрительный, но вы тоже можете его рассмотреть.
Result r = null;
YourAsyncMethod()
.ContinueWith(t =>
{
r = t.Result;
})
.Wait();
Ответ 6
Чтобы предотвратить Task.Run()
блокировки, я всегда стараюсь использовать Task.Run()
когда мне нужно синхронно вызывать асинхронный метод, о котором упоминает @Heinzi.
Однако метод должен быть модифицирован, если асинхронный метод использует параметры. Например Task.Run(GenerateCodeAsync("test")).Result
выдает ошибку:
Аргумент 1: невозможно преобразовать из " System.Threading.Tasks.Task<string>
" в "System.Action"
Это можно назвать так:
string code = Task.Run(() => GenerateCodeAsync("test")).Result;
Ответ 7
Другой способ может быть, если вы хотите дождаться завершения задачи:
var t = GenerateCodeService.GenerateCodeAsync();
Task.WhenAll(t);
string code = t.Result;
Ответ 8
Если у вас есть асинхронный метод RefreshList, вы можете вызвать этот асинхронный метод из неасинхронного метода, как показано ниже.
Task.Run(async () => { await RefreshList(); });