异步/等待返回呼叫链如何工作? [英] How does the async/await return callchain work?

查看:77
本文介绍了异步/等待返回呼叫链如何工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

最近遇到一种情况,我有一个ASP.NET WebAPI控制器,该控制器需要在其action方法内执行对另一个REST服务的两个Web请求。我已经编写了代码,以将功能完全分离为单独的方法,看起来像下面的示例:

 公共类FooController: ApiController 
{

public IHttpActionResult Post(string value)
{
var results = PerformWebRequests();
//在这里做其他事情...
}

private IEnumerable< string> PerformWebRequests()
{
var result1 = PerformWebRequest( service1 / api / foo);
var result = PerformWebRequest( service2 / api / foo);

返回新字符串[] {result1,result2};
}

私有字符串PerformWebRequest(string api)
{
using(HttpClient client = new HttpClient())
{
//调用其他Web API并在此处返回值...
}
}

}

因为我使用的是 HttpClient ,所以所有Web请求都必须是异步的。我之前从未使用过async / await,因此我开始天真地添加关键字。首先,我在 PerformWebRequest(string api)方法中添加了 async 关键字,但随后调用者抱怨<$ c为了使用 await ,$ c> PerformWebRequests()方法也必须异步 。所以我做了 async ,但是现在该方法的调用者也必须是 async ,依此类推。



我想知道的是,一切必须标记为 async 才能正常工作到兔子洞的下方吗?肯定会出现某些事情必须同步运行的情况,在这种情况下,如何安全处理?我已经读过调用 Task.Result 是一个坏主意,因为它可能导致死锁。

解决方案方案


我想知道的是,必须将所有标记为异步的
都放到兔子洞的下方多远才能起作用?肯定会有一个必须同步运行
的点


不,应该不存在任何东西同步运行,这就是异步的全部意义。短语一路异步 实际上表示一路同步



当您异步处理消息时,您将在真正异步方法运行时让消息循环处理请求,因为当您深入到兔子漏洞中时, 没有线程



例如,当您有一个异步按钮单击事件处理程序时:

 私有异步无效Button_Click (对象发送者,RoutedEventArgs e)
{
等待DoWorkAsync();
//在这里做更多的事情
}

private Task DoWorkAsync()
{
return Task.Delay(2000); //伪造的工作。
}

单击按钮时,将同步运行,直到第一个等待。一旦命中,该方法将把控制权交还给调用者,这意味着按钮事件处理程序将释放UI线程,这将释放消息循环以同时处理更多请求。



使用 HttpClient 也是一样。例如,当您拥有:

 公共异步任务< IHttpActionResult> Post(字符串值)
{
var results = await PerformWebRequests();
//在这里做其他事情...
}

私有异步任务< IEnumerable< string>> PerformWebRequests()
{
var result1 = await PerformWebRequestAsync( service1 / api / foo);
var result = await PerformWebRequestAsync( service2 / api / foo);

返回新字符串[] {result1,result2};
}

私有异步字符串PerformWebRequestAsync(string api)
{
using(HttpClient client = new HttpClient())
{
等待client.GetAsync(api);
}

//更多工作..
}

查看 async 关键字如何一直上升到处理 POST 请求的主要方法。这样,虽然异步http请求由网络设备驱动程序处理,但是您的线程返回到ASP.NET ThreadPool,并且可以同时自由处理更多请求。



控制台应用程序是一种特殊情况,因为当 Main 方法终止时,除非您旋转新的前景线程,否则该应用程序将终止。在那里,必须确保如果唯一的调用是异步调用,则必须显式使用 Task.Wait Task.Result 。但是在那种情况下,默认的 SynchronizationContext ThreadPoolSynchronizationContext ,在这种情况下不会造成死锁。 / p>

总而言之,异步方法不应在堆栈顶部同步处理,除非存在奇异的用例(例如控制台应用程序),它们应该一直异步流动,以便在可能的情况下释放线程。


I had a situation recently where I had an ASP.NET WebAPI controller that needed to perform two web requests to another REST service inside its action method. I had written my code to have functionality separated cleanly into separate methods, which looked a little like this example:

public class FooController : ApiController
{

    public IHttpActionResult Post(string value)
    {
        var results = PerformWebRequests();
        // Do something else here...
    }

    private IEnumerable<string> PerformWebRequests()
    {
        var result1 = PerformWebRequest("service1/api/foo");
        var result = PerformWebRequest("service2/api/foo");

        return new string[] { result1, result2 };
    }

    private string PerformWebRequest(string api)
    {
        using (HttpClient client = new HttpClient())
        {
            // Call other web API and return value here...
        }
    }

}

Because I was using HttpClient all web requests had to be async. I've never used async/await before so I started naively adding in the keywords. First I added the async keyword to the PerformWebRequest(string api) method but then the caller complained that the PerformWebRequests() method has to be async too in order to use await. So I made that async but now the caller of that method must be async too, and so on.

What I want to know is how far down the rabbit hole must everything be marked async to just work? Surely there would come a point where something has to run synchronously, in which case how is that handled safely? I've already read that calling Task.Result is a bad idea because it could cause deadlocks.

解决方案

What I want to know is how far down the rabbit hole must everything be marked async to just work? Surely there would come a point where something has to run synchronously

No, there shouldn't be a point where anything runs synchronously, and that is what async is all about. The phrase "async all the way" actually means all the way up the call stack.

When you process a message asynchronously, you're letting your message loop process requests while your truly asynchronous method runs, because when you go deep down the rabit hole, There is no Thread.

For example, when you have an async button click event handler:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    await DoWorkAsync();
    // Do more stuff here
}

private Task DoWorkAsync()
{
    return Task.Delay(2000); // Fake work.
}

When the button is clicked, runs synchronously until hitting the first await. Once hit, the method will yield control back to the caller, which means the button event handler will free the UI thread, which will free the message loop to process more requests in the meanwhile.

The same goes for your use of HttpClient. For example, when you have:

public async Task<IHttpActionResult> Post(string value)
{
    var results = await PerformWebRequests();
    // Do something else here...
}

private async Task<IEnumerable<string>> PerformWebRequests()
{
    var result1 = await PerformWebRequestAsync("service1/api/foo");
    var result = await PerformWebRequestAsync("service2/api/foo");

    return new string[] { result1, result2 };
}

private async string PerformWebRequestAsync(string api)
{
    using (HttpClient client = new HttpClient())
    {
        await client.GetAsync(api);
    }

    // More work..
}

See how the async keyword went up all the way to the main method processing the POST request. That way, while the async http request is handled by the network device driver, your thread returns to the ASP.NET ThreadPool and is free to process more requests in the meanwhile.

A Console Application is a special case, since when the Main method terminates, unless you spin a new foreground thread, the app will terminate. There, you have to make sure that if the only call is an async call, you'll have to explicitly use Task.Wait or Task.Result. But in that case the default SynchronizationContext is the ThreadPoolSynchronizationContext, where there isn't a chance to cause a deadlock.

To conclude, async methods shouldn't be processed synchronously at the top of the stack, unless there is an exotic use case (such as a Console App), they should flow asynchronously all the way allowing the thread to be freed when possible.

这篇关于异步/等待返回呼叫链如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆