在异步函数上调用结果是不是会导致无限期阻塞?

Posted

技术标签:

【中文标题】在异步函数上调用结果是不是会导致无限期阻塞?【英文标题】:Does Calling Result on an Async function cause block indefinitely?在异步函数上调用结果是否会导致无限期阻塞? 【发布时间】:2015-04-02 02:33:56 【问题描述】:

我是 C# 的新手,来自沉重的 java 背景,但仍在试图弄清楚 await 和 async 究竟是如何工作的,这让我很困惑:

假设我有一个这样的函数(下面的黑客代码仅用于实验):

public static bool CheckInternetConnection()

    Task<bool> result = CheckInternetConnectionAsync();
    return result.Result;


public async static Task<bool> CheckInternetConnectionAsync()

    if (NetworkInterface.GetIsNetworkAvailable())
    
        SomeClass details = await ReturnARunningTask();
        return details.IsSuccessful;
    
    return false;


public Task<SomeClass> ReturnARunningTask()

    return Task.Run(() => checkInternet());

在主执行线程中,如果我这样调用:

CheckInternetConnection();

它将无限期地阻塞,我的假设是控制将 CheckInternetConnectionAsync() 留在“await”关键字并阻塞“.Result”。当 await 恢复时,主线程已经被阻塞并保持阻塞状态

我假设的原因是我可以看到任务完成并返回,但是await之后的代码永远不会执行

但是,如果我这样做:

bool result = Task.Run(() => CheckInternetConnection()).Result;

然后执行await语句后的代码,主线程继续执行

我的预期是它也会阻塞,因为主线程会在中间任务上被阻塞。中间任务会在 .Result 上被阻塞

所以..有什么不同?在这种情况下,await之后的代码会被执行吗?

【问题讨论】:

这是什么类型的应用程序?控制台,wpf? 它是一个在 android 上运行的 xamarin 应用程序,用 C# 编写... 【参考方案1】:

在 UI 环境中,您有一个特殊的单线程 SynchronizationContext,它运行 UI 线程上的所有内容。当您 await 一个任务时,该上下文被捕获,当任务完成时,该方法在该捕获的上下文中恢复(这可以使用 ConfigureAwait 进行配置:

SomeClass details = await ReturnARunningTask().ConfigureAwait(false);

在您的情况下,当您在未完成的任务上使用 Task.Result 属性时,您会同步阻塞 UI 线程,因此当 CheckInternetConnectionAsyncReturnARunningTask 完成后尝试在 SynchronizationContext 上恢复时,它可以吨。 UI 线程在等待CheckInternetConnectionAsync 任务时被阻塞,该任务又等待 UI 线程,因此出现死锁(无限期阻塞)。

使用Task.Run(() =&gt; CheckInternetConnection()).Result; 时的区别在于,使用Task.Run 卸载要在ThreadPool 线程上完成的工作。这些线程没有 SynchronizationContext,因此当 UI 线程在 .Result 上被阻塞时,任务可以完成,因为它不需要 UI 线程来执行此操作。

不鼓励阻塞异步操作 (sync over async),因为它会阻塞线程并使应用程序的响应性和可扩展性降低,并且可能导致死锁。 您应该一直使用async-await


注意:不要执行“通过 async 同步”,您应该让同步版本同步,如果您需要将 CPU 密集型工作卸载到不同线程的 async 版本,请使用 Task.Run

public static bool CheckInternetConnection()

    return checkInternet().IsSuccessful;


public async static Task<bool> CheckInternetConnectionAsync()

    if (NetworkInterface.GetIsNetworkAvailable())
    
        return await Task.Run(() => CheckInternetConnection());
    
    return false;

【讨论】:

问题,所以如果我从 UI 线程调用 await,而后者又调用 await 等等并且不使用任何 TPL 东西,UI SyncContext 是否总是在 await 时传递? @MatthewYang 是的。除非您使用Task,否则它将一次又一次地传递。运行或 ConfigureAwait(false)

以上是关于在异步函数上调用结果是不是会导致无限期阻塞?的主要内容,如果未能解决你的问题,请参考以下文章

IO概念解析------同步异步阻塞非阻塞

python 37 同步异步调用

异步/同步/阻塞/非阻塞

gevent.queue

当你在调用函数时,你在干什么,被调用函数在干什么?--同步与异步,阻塞与非阻塞

同步异步阻塞非阻塞 总结