【问题标题】:async Main in C# and blocking async Http calls [duplicate]C# 中的 async Main 和阻塞异步 Http 调用 [重复]
【发布时间】:2019-05-25 18:02:30
【问题描述】:

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

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 中的所有方法都按照它们被调用的顺序执行,因为异步执行模型是并发的?

using static System.Console;
using System.Net.Http;
using System.Threading.Tasks;

class Start
{
    const string serviceURL = "https://google.com";
    const string slowDown = "http://slowwly.robertomurray.co.uk/delay/10000/url/";
    const string slowURL = slowDown + serviceURL;
    const string OkURL = serviceURL;

    static async Task FetchSlowAsync()
    {
        await Console.Out.WriteLineAsync("FetchSlowAsync start");
        await new HttpClient().GetStringAsync(slowURL); //BLOCKS MAIN THREAD FOR 10 seconds        
        await Console.Out.WriteLineAsync("FetchSlowAsync got data!");
    }

    static async Task FetchAsync()
    {
        await Console.Out.WriteLineAsync("FetchAsync start");
        await new HttpClient().GetStringAsync(OkURL);
        await Console.Out.WriteLineAsync("FetchAsync got data!");        
    }

    static async Task FetchBingAsync()
    {
        await Console.Out.WriteLineAsync("FetchBingAsync start");
        await new HttpClient().GetStringAsync("https://bing.com");
        await Console.Out.WriteLineAsync("FetchBingAsync got data!");
    }

    static async Task Main()
    {
        await FetchSlowAsync();
        await FetchBingAsync();
        await FetchAsync();

        await System.Console.Out.WriteLineAsync("All done!");
    }
}

【问题讨论】:

  • 您介意我重构您的代码以不包含本地方法吗?这里不需要它们,它们只是增加了复杂性。
  • 接下来,听起来您希望这些并行发生。为什么?你总是在等待你刚刚开始的任何异步任务。例如,在调用FetchBingAsync() 之前,您正在等待FetchSlowAsync() 返回的任务。
  • (如果你愿意,我可以更详细地写出来作为答案。但这真的取决于你是否/为什么期望事情并行发生。)
  • @JonSkeet 当然,乔恩,我不介意。添加该 sn-p 仅用于演示目的,因为我对执行的期望与输出有很大不同。
  • 好吧,也许您应该阅读有关 async、await 和 TPL 的文档。 From the docs "await 运算符应用于异步方法中的任务,以在方法执行中插入暂停点,直到等待的任务完成"

标签: c# concurrency .net-core async-await dotnet-httpclient


【解决方案1】:

目前,您正在等待从 FetchSlowAsync() 返回的任务完成,然后再继续调用 FetchBingAsync 等。您正在通过等待任务来完成此操作,此处:

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!

【讨论】:

  • 我想我对 C# 的 async 的期望比它实际所能提供的要多一点。我认为如果await 超过 IL 指令或时间阈值,它会切换到主线程,它会选择下一个异步方法来执行(以我的代码为例)。
  • @alexeyhaidamaka:我想说这不是“期待更多”,而是“期待非常不同”——而且我认为这会更糟糕,因为它的可预测性。如果await 运算符可能只是觉得无聊并继续执行下一条语句,那么如果您使用await 运算符的结果,您希望它做什么?了解 async/await 的实际作用非常重要,因为这样你就可以决定你想要的行为。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-09-16
  • 1970-01-01
  • 2017-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多