同步调用异步方法

Cat*_*lin 216 c# asp.net asynchronous task-parallel-library async-await

我有一个async方法:

public async Task<string> GenerateCodeAsync()
{
    string code = await GenerateCodeService.GenerateCodeAsync();
    return code;
}
Run Code Online (Sandbox Code Playgroud)

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

如何在不必复制GenerateCodeAsync方法的情况下执行此操作以使其同步工作?

更新

然而找不到合理的解决方案

但是,我看到HttpClient已经实现了这种模式

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

    // sync
    HttpResponseMessage responseSync = client.GetAsync(url).Result;
}
Run Code Online (Sandbox Code Playgroud)

Hei*_*nzi 266

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

string code = GenerateCodeAsync().Result;
Run Code Online (Sandbox Code Playgroud)

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

  • 如果调用`result`会导致死锁,那么当*是*时,获得结果是否安全?每个异步调用都需要`Task.Run`或`ConfigureAwait(false)`? (30认同)
  • 注意:如果调用者在线程池本身上,则调用`.Result`仍然会死锁.假设一个场景,其中线程池大小为32,正在运行32个任务,并且`Wait()/ Result`正在等待尚未安排的第33个任务,该任务想要在其中一个等待线程上运行. (10认同)
  • ASP.NET中没有"主线程"(与GUI应用程序不同),但由于[how](http://stackoverflow.com/q/23062154/1768303)`AspNetSynchronizationContext.Post`序列化,死锁仍然存在异步延续:`task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action)); _lastScheduledTask = newTask;` (4认同)
  • @RobertHarvey:如果你无法控制async方法的实现,那么你应该用`Task.Run`来包装它以保持安全.或者使用类似[`WithNoContext`](http://stackoverflow.com/q/28410046/1768303)的内容来减少冗余线程切换. (4认同)

小智 53

您应该获取awaiter(GetAwaiter())并结束等待异步任务(GetResult())的完成.

string code = GenerateCodeAsync().GetAwaiter().GetResult();
Run Code Online (Sandbox Code Playgroud)

  • 我们使用此解决方案遇到了死锁.被警告. (34认同)
  • [MSDN`Task.GetAwaiter`](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.getawaiter.aspx):此方法用于编译器使用而不是用于应用代码. (5认同)

Fai*_*yaz 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" ;
    }
Run Code Online (Sandbox Code Playgroud)

  • 谢谢你指出,是的,它返回任务类型.将"string sCode"替换为Task <string>或var sCode应解决它.添加完整的编译代码以方便. (5认同)

nos*_*tio 20

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

正如另一个答案所暗示的那样,它可能与GenerateCodeAsync().ResultGenerateCodeAsync().Wait()有关.这将阻止当前线程直到GenerateCodeAsync完成.

但是,你的问题用标记,你也留下了评论:

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

我的观点是,你不应该阻止 ASP.NET中的异步方法.这将降低Web应用程序的可伸缩性,并可能造成死锁(当发布await内部延续时).使用将某些内容卸载到池线程然后阻塞会进一步损害可伸缩性,因为它会导致+1更多线程来处理给定的HTTP请求.GenerateCodeAsyncAspNetSynchronizationContextTask.Run(...).Result

ASP.NET有内置的异步方法的支持,无论是通过异步控制器(在ASP.NET MVC和Web API)或直接通过AsyncManagerPageAsyncTask经典ASP.NET.你应该使用它.有关详细信息,请查看此答案.

  • @Noserato,问题是从同步调用异步方法.有时您无法更改您实施的API.假设你从一些第三方框架"A"(你不能将框架重写为异步方式)实现一些同步接口,但是你试图在你的实现中使用的第三方库"B"只是异步的.产生的产品也是库,可以在任何地方使用,包括ASP.NET等. (9认同)
  • 并非所有`.NET MVC`过滤器都支持异步代码,例如`IAuthorizationFilter`,所以我不能一直使用`async` (4认同)
  • @RaraituL,一般来说,你不要混合异步和同步代码,选择euther模型.你可以同时实现`SaveChangesAsync`和`SaveChanges`,只要确保它们不会在同一个ASP.NET项目中被调用. (3认同)
  • @Noseratio这是一个不切实际的目标.有太多具有异步和同步代码的库以及仅使用一个模型的情况是不可能的.例如,MVC ActionFilters不支持异步代码. (3认同)

Vit*_*nov 18

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));
    }
Run Code Online (Sandbox Code Playgroud)

现在让我们看看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();
    }
Run Code Online (Sandbox Code Playgroud)

所以,这是异步方法的包装器.请不要从结果中读取数据 - 它可能会阻止您在ASP中的代码.

还有另外一种方式 - 这对我来说很可疑,但你也可以考虑它

  Result r = null;

            YourAsyncMethod()
                .ContinueWith(t =>
                {
                    r = t.Result;
                })
                .Wait();
Run Code Online (Sandbox Code Playgroud)

  • 您认为第二种方式是什么问题? (3认同)

kam*_*eet 13

该线程上的大多数答案要么很复杂,要么会导致死锁。

下面的方法很简单,它会避免死锁,因为我们正在等待任务完成,然后才得到它的结果——

var task = Task.Run(() => GenerateCodeAsync()); 
task.Wait();
string code = task.Result;
Run Code Online (Sandbox Code Playgroud)

此外,这是对 MSDN 文章的引用,该文章谈论了完全相同的事情- https://blogs.msdn.microsoft.com/jpsanders/2017/08/28/asp-net-do-not-use-task-result-主上下文/

  • 尽管有 Microsoft 参考,但它的“Task.Run”使此工作有效(当在 ASP.NET 上下文线程上运行时)。`Wait` 是无关紧要的 - 当任务尚未完成时,`Result` 会执行相同的操作。请参阅[此问答](/sf/ask/2516355621/)。 (2认同)

Ogg*_*las 6

为了防止死锁,我总是尝试使用Task.Run()@Heinzi提到的同步调用异步方法.

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

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

这可以像这样调用:

string code = Task.Run(() => GenerateCodeAsync("test")).Result;
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

208182 次

最近记录:

5 年,10 月 前