Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/24.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 异步/等待返回调用链是如何工作的?_C#_.net_Asynchronous_Async Await_Task Parallel Library - Fatal编程技术网

C# 异步/等待返回调用链是如何工作的?

C# 异步/等待返回调用链是如何工作的?,c#,.net,asynchronous,async-await,task-parallel-library,C#,.net,Asynchronous,Async Await,Task Parallel Library,我最近遇到了一个情况,我有一个ASP.NETWebAPI控制器,需要在其action方法中对另一个REST服务执行两个web请求。我编写的代码将功能清晰地分离到不同的方法中,看起来有点像下面的示例: public class FooController : ApiController { public IHttpActionResult Post(string value) { var results = PerformWebRequests();

我最近遇到了一个情况,我有一个ASP.NETWebAPI控制器,需要在其action方法中对另一个REST服务执行两个web请求。我编写的代码将功能清晰地分离到不同的方法中,看起来有点像下面的示例:

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...
        }
    }

}
公共类FooController:ApicController
{
公共IHttpActionResult Post(字符串值)
{
var结果=PerformWebRequests();
//在这里做点别的。。。
}
私有IEnumerable PerformWebRequests()
{
var result1=PerformWebRequest(“服务1/api/foo”);
var结果=PerformWebRequest(“服务2/api/foo”);
返回新字符串[]{result1,result2};
}
私有字符串PerformWebRequest(字符串api)
{
使用(HttpClient=new HttpClient())
{
//调用其他web API并在此处返回值。。。
}
}
}
因为我使用的是
HttpClient
,所以所有web请求都必须是异步的。我以前从未使用过async/await,所以我开始天真地添加关键字。首先,我将
async
关键字添加到
PerformWebRequest(string api)
方法中,但是调用方抱怨
PerformWebRequests()
方法也必须是
async
才能使用
wait
。所以我做了
async
,但是现在该方法的调用方也必须是
async
,以此类推

我想知道的是,所有东西都必须标记为
async
才能正常工作,离兔子洞有多远?肯定会有一个点,事情必须同步运行,在这种情况下,如何安全地处理?我已经读过调用
任务.Result
是个坏主意,因为它可能会导致死锁。

您需要“一路异步”到调用堆栈的最顶端,在那里您可以到达一个可以处理所有异步请求的消息循环

我想知道的是,一切都必须在兔子洞的下面多远 是否将异步标记为仅工作?肯定会有这样一个时刻 有些东西必须同步运行

不,任何东西都不应该同步运行,这就是异步的意义所在。短语“一路异步”实际上意味着调用堆栈上的一路异步

当您异步处理消息时,您让消息循环处理请求,而真正的异步方法运行,因为当您深入rabit洞时

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

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

private Task DoWorkAsync()
{
    return Task.Delay(2000); // Fake work.
}
单击按钮时,将同步运行,直到单击第一个
wait
。一旦命中,该方法将把控制权交还给调用方,这意味着按钮事件处理程序将释放UI线程,这将释放消息循环以同时处理更多请求

您使用的
HttpClient
也是如此。例如,当您有:

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..
}
公共异步任务Post(字符串值)
{
var结果=等待性能测试();
//在这里做点别的。。。
}
专用异步任务PerformWebRequests()
{
var result1=等待PerformWebRequestAsync(“服务1/api/foo”);
var结果=等待PerformWebRequestAsync(“服务2/api/foo”);
返回新字符串[]{result1,result2};
}
专用异步字符串PerformWebRequestAsync(字符串api)
{
使用(HttpClient=new HttpClient())
{
等待client.GetAsync(api);
}
//更多的工作。。
}
查看
async
关键字如何一直上升到处理
POST
请求的主方法。这样,当异步http请求由网络设备驱动程序处理时,线程返回到ASP.NET线程池,同时可以自由处理更多请求

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


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

我正在讨论这是否符合复制条件:你可以在channel9上观看关于异步的精彩视频-另一个优秀的资源是Jon Skeet的Eduancy系列博文好答案。作为一种优化,还可以使对
PerformWebRequestAsync
的两个调用可以并行进行,并通过
Task.whalll
等待。当然,这是假设第一次调用的结果不需要第二次调用的请求。@MattJohnson感谢matt,我想到了这一点,但不想太多地更改OP的原始代码,以将其置于答案中。谢谢@YuvalItzchakov,这对我解释了很多,特别是在
Main()中使用时
方法(我也很想知道)。在我看来,虽然
async/await
使这一切变得容易得多,但在插入之前,还需要考虑一些设计方面的考虑因素,比如理解添加一个
async
方法可能需要更改多少代码。