同步调用异步方法


230

我有一个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
我希望有一个更简单的解决方案,认为asp.net比
编写这么多

为什么不仅仅拥抱异步代码?理想情况下,您需要更多的异步代码,而不是更少。
Paulo Morgado 2014年

53
[为什么不仅仅包含异步代码?]哈,这可能恰恰是因为人们正在拥抱异步代码,所以随着项目的大部分转换,他们需要这种解决方案!您无法在一天内重建罗马。
Nicholas Petersen 2014年

1
@NicholasPetersen有时3rd-party库可以强迫您执行此操作。在FluentValidation之外的WithMessage方法中构建动态消息的示例。由于库设计,因此没有异步API-WithMessage重载是静态的。将动态参数传递给WithMessage的其他方法很奇怪。
H.Wojtowicz

Answers:


278

您可以访问Result任务的属性,这将导致线程阻塞,直到结果可用为止:

string code = GenerateCodeAsync().Result;

注意:在某些情况下,这可能导致死锁:您Result对主线程的调用被阻塞,从而阻止了异步代码的其余部分执行。您可以使用以下选项来确保不会发生这种情况:

并不意味着您应该.ConfigureAwait(false)在所有异步调用之后不加思索地添加!有关为什么和何时使用的详细分析.ConfigureAwait(false),请参见以下博客文章:


33
如果调用result风险死锁,那么当它的安全得到结果呢?每个异步调用都需要Task.Run还是ConfigureAwait(false)
罗伯特·哈维

4
ASP.NET中没有“主线程”(不同于GUI应用程序),但是由于如何 AspNetSynchronizationContext.Post序列化异步延续,死锁仍然可能发生:Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action)); _lastScheduledTask = newTask;
noseratio 2015年

4
@RobertHarvey:如果您无法控制要阻止的异步方法的实现,那么可以,请包装起来以Task.Run确保安全。或者使用类似的方法WithNoContext来减少冗余线程切换。
noseratio

10
注意:.Result如果调用者位于线程池本身上,则调用仍可能死锁。假设有一个线程池大小为32,并且32个任务正在运行并且Wait()/Result正在等待要在其中一个等待线程上运行的尚未调度的第33个任务上等待的情况。
Warty

55

您应该获得等待者(GetAwaiter()),并结束等待异步任务完成的等待(GetResult())。

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

37
使用此解决方案时,我们陷入了僵局。被警告。
奥利弗·

6
MSDNTask.GetAwaiter:此方法供编译器使用,而不是在应用程序代码中使用。
foka

我仍然出现错误对话框弹出(违背我的意愿),并带有“切换至”或“重试”按钮。但是,该调用实际上会执行并返回正确的响应。
乔纳森·汉森

30

您应该能够使用委托,lambda表达式来完成此任务

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

该代码段将无法编译。Task.Run的返回类型是Task。有关完整说明,请参见此MSDN博客
2015年

5
感谢您指出,是的,它返回任务类型。将“字符串sCode”替换为Task <string>或var sCode应该可以解决该问题。轻松添加完整的编译代码。
Faiyaz

20

我需要从同步方法中调用此方法。

另一个答案表明,可以使用GenerateCodeAsync().ResultGenerateCodeAsync().Wait()。这将阻塞当前线程,直到GenerateCodeAsync完成为止。

但是,您的问题被标记为 ,您还留下了评论:

我希望有一个更简单的解决方案,认为asp.net比编写这么多代码行要容易得多

我的观点是,您不应在ASP.NET中阻塞异步方法。这将降低Web应用程序的可伸缩性,并且可能会导致死锁(将await内部的延续GenerateCodeAsync发布到时AspNetSynchronizationContext)。使用Task.Run(...).Result卸载东西池线程,然后块将甚至伤害更多的可扩展性,因为它会带来更多的+1线程来处理一个给定的HTTP请求。

ASP.NET内置了对异步方法的支持,可以通过异步控制器(在ASP.NET MVC和Web API中),也可以直接通过AsyncManagerPageAsyncTask在经典ASP.NET中。您应该使用它。有关更多详细信息,请检查此答案


我正在覆盖的SaveChanges()方法DbContext,在这里我将调用异步方法,因此不幸的是,在这种情况下,异步控制器无法为我提供帮助
Catalin 2014年

3
@RaraituL,通常,您不会混合使用异步代码和同步代码,而是选择其他模型。您可以同时实现SaveChangesAsyncSaveChanges,只需确保在同一ASP.NET项目中不会同时调用它们。
noseratio 2014年

4
.NET MVC例如IAuthorizationFilter,并非所有过滤器都支持异步代码,因此我不能async一路使用
Catalin

3
@Noseratio这是一个不现实的目标。有太多具有异步和同步代码的库,以及无法仅使用一种模型的情况。例如,MVC ActionFilters不支持异步代码。
贾斯汀·斯基尔斯

9
@Noserato,问题是关于从同步调用异步方法。有时您无法更改要实现的API。假设您从某个第三方框架“ A”(不能将框架重写为异步方式)实现某个同步接口,但是您尝试在实现中使用的第三方库“ B”只有异步。还得到的产物也库,并且可以在任何地方使用包括ASP.NET等
dimzon

19

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

3
您认为第二种建议是什么问题?
大卫·克拉克

@DavidClarke可能是从多个线程无锁访问非易失性变量的线程安全问题。
Theodor Zoulias '19

9

为了避免死锁,我总是尝试Task.Run()在必须同步调用@Heinzi提到的异步方法时使用。

但是,如果异步方法使用参数,则必须修改该方法。例如Task.Run(GenerateCodeAsync("test")).Result给出错误:

参数1:无法从“ System.Threading.Tasks.Task<string>” 转换为“ System.Action”

可以这样称呼它:

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

5

该线程上的大多数答案很复杂,否则将导致死锁。

以下方法很简单,并且可以避免死锁,因为我们正在等待任务完成,然后才能得到结果-

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- 在上下文中/


0

我更喜欢非阻塞方法:

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

0

我正在使用这种方法:

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

        return task.Result;
    }

-1

另一种方法是,如果您要等到任务完成,请执行以下操作:

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

1
这是完全错误的。WhenAll还返回一个您正在等待的任务。
罗伯·施密特

-1

编辑:

Task具有Wait方法Task.Wait(),该方法等待“承诺”解决,然后继续执行,从而使其同步。例:


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

String mySyncMethod() {

    return MyAsyncMethod().Wait();
}

3
请详细说明您的答案。如何使用?回答问题有何具体帮助?
Scratte

-2

如果您有一个称为“ RefreshList ” 的异步方法,则可以从如下所示的非异步方法中调用该异步方法。

Task.Run(async () => { await RefreshList(); });
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.