C#中的异步Main和阻止异步Http调用

C#中的异步Main和阻止异步Http调用,c#,concurrency,.net-core,async-await,dotnet-httpclient,C#,Concurrency,.net Core,Async Await,Dotnet Httpclient,我对我从我认为是纯异步程序中得到的输出感到非常困惑。 正如您所观察到的,没有明显的反模式(我希望)和阻塞调用 slowURL限制服务器响应10秒。在控制台中运行代码时,我确实通过以10秒超时运行对本地服务器的调用来确认FetchSlowAsync方法调用有效地阻止了主线程10秒 我希望TaskScheduler不会以顺序方式安排调用,而是始终随机确定方法调用顺序。唉,输出总是确定性的 FetchSlowAsync start FetchSlowAsync got data! FetchAsync

我对我从我认为是纯异步程序中得到的输出感到非常困惑。 正如您所观察到的,没有明显的反模式(我希望)和阻塞调用

slowURL
限制服务器响应10秒。在控制台中运行代码时,我确实通过以10秒超时运行对本地服务器的调用来确认
FetchSlowAsync
方法调用有效地阻止了主线程10秒

我希望TaskScheduler不会以顺序方式安排调用,而是始终随机确定方法调用顺序。唉,输出总是确定性的

FetchSlowAsync start
FetchSlowAsync got data!
FetchAsync start
FetchAsync got data!
FetchBingAsync start
FetchBingAsync got data!
All done!
我的问题是:是什么提示
FetchSlowAsync
阻止而不是TaskScheduler执行上下文切换到另一个异步方法,并在完成后返回到它

前一个问题之后的下一个问题是:既然异步执行模型是并发的,为什么
async Main
中的所有方法的执行顺序与调用顺序相同

使用静态系统控制台;
使用System.Net.Http;
使用System.Threading.Tasks;
开课
{
常量字符串serviceURL=”https://google.com";
常量字符串减速=”http://slowwly.robertomurray.co.uk/delay/10000/url/";
const string slowURL=减速+服务URL;
常量字符串OkURL=serviceURL;
静态异步任务FetchSlowAsync()
{
等待Console.Out.WriteLineAsync(“FetchSlowAsync启动”);
等待新的HttpClient().GetStringAsync(slowURL);//阻塞主线程10秒
等待Console.Out.WriteLineAsync(“FetchSlowAsync获得数据!”);
}
静态异步任务FetchAsync()
{
等待Console.Out.WriteLineAsync(“FetchAsync启动”);
等待新的HttpClient().GetStringAsync(OkURL);
等待Console.Out.WriteLineAsync(“FetchAsync获得数据!”);
}
静态异步任务FetchBingAsync()
{
等待Console.Out.WriteLineAsync(“FetchBingAsync启动”);
等待新的HttpClient()。GetStringAsync(“https://bing.com");
等待Console.Out.WriteLineAsync(“FetchBingAsync获得数据!”);
}
静态异步任务Main()
{
等待FetchSlowAsync();
等待fetchbingsync();
等待FetchAsync();
等待System.Console.Out.WriteLineAsync(“全部完成!”);
}
}

当前,您正在等待从
FetchSlowAsync()
返回的任务完成,然后再调用
fetchbingsync
等。您通过等待任务来完成此操作,如下所示:

await FetchSlowAsync();
await FetchBingAsync();
await FetchAsync();
如果您不想在开始Bing抓取等操作之前等待慢速抓取完成,您可以记住他们返回的任务,稍后再等待:

static async Task Main()
{
    // Start all three tasks
    Task t1 = FetchSlowAsync();
    Task t2 = FetchBingAsync();
    Task t3 = FetchAsync();

    // Then wait for them all to finish
    await Task.WhenAll(t1, t2, t3);

    await Console.Out.WriteLineAsync("All done!");
}
现在你得到了我认为你期望的结果。例如,在我的机器上运行一次:

FetchSlowAsync start
FetchBingAsync start
FetchAsync start
FetchAsync got data!
FetchBingAsync got data!
FetchSlowAsync got data!
All done!

目前,您正在等待从
FetchSlowAsync()
返回的任务完成,然后再调用
fetchbingsync
等。您可以通过等待任务来完成此操作,如下所示:

await FetchSlowAsync();
await FetchBingAsync();
await FetchAsync();
如果您不想在开始Bing抓取等操作之前等待慢速抓取完成,您可以记住他们返回的任务,稍后再等待:

static async Task Main()
{
    // Start all three tasks
    Task t1 = FetchSlowAsync();
    Task t2 = FetchBingAsync();
    Task t3 = FetchAsync();

    // Then wait for them all to finish
    await Task.WhenAll(t1, t2, t3);

    await Console.Out.WriteLineAsync("All done!");
}
现在你得到了我认为你期望的结果。例如,在我的机器上运行一次:

FetchSlowAsync start
FetchBingAsync start
FetchAsync start
FetchAsync got data!
FetchBingAsync got data!
FetchSlowAsync got data!
All done!

你介意我重构你的代码,不包括本地方法吗?这里不需要它们,它们只是增加了复杂性。下一步,听起来你希望这些并行发生。为什么?您总是在等待刚刚开始的任何异步任务。例如,在调用
fetchbingsync()
之前,您正在等待
FetchSlowAsync()
返回的任务。(如果您愿意,我可以把它写得更详细,作为一个答案。但这真的取决于您是否/为什么希望事情同时发生。)@jonsket当然,Jon,我不介意。这段代码只是为了演示而添加的,因为我对执行的期望与输出有很大的不同。“await运算符应用于异步方法中的任务,以在该方法的执行中插入挂起点,直到等待的任务完成”您介意我重构您的代码以不包含本地方法吗?这里不需要它们,它们只是增加了复杂性。下一步,听起来你希望这些并行发生。为什么?您总是在等待刚刚开始的任何异步任务。例如,在调用
fetchbingsync()
之前,您正在等待
FetchSlowAsync()
返回的任务。(如果您愿意,我可以把它写得更详细,作为一个答案。但这真的取决于您是否/为什么希望事情同时发生。)@jonsket当然,Jon,我不介意。这段代码只是为了演示而添加的,因为我对执行的期望与输出有很大的不同。“await运算符应用于异步方法中的任务,以在该方法的执行过程中插入一个挂起点,直到等待的任务完成。”我想我对C#的async的期望比它实际能给出的要高一些。我认为,如果
await
超过IL指令或时间阈值,它将切换到主线程,并将选择下一个异步方法执行(以我的代码为例)。@alexeyhaidamaka:我会说,这不是“期望更多”,而是“期望非常不同”——我认为情况会更糟,因为它的可预测性要低得多。如果一个
await
操作符可能会感到厌烦并继续下一个语句,那么如果您使用
await
操作符的结果,您希望它做什么?理解async/await的实际功能非常重要,因为这样你就可以决定你想要的行为