在异步/等待中阻塞主线程

3
我想将我的基础仓库类变成异步的,但是我遇到了一些问题。我在我的C#应用程序中使用Dapper ORM。
基础方法
protected async Task<List<T>> Read<T>(CommandDefinition cmd) {
    using(SqlConnection myCon = new SqlConnection(Config.DBConnection)) {
        await myCon.OpenAsync();

        IEnumerable<T> results = await myCon.QueryAsync<T>(cmd);

        List<T> retVal = results.ToList();

        myCon.Close();

        return retVal;
    }
}

调用方法

public List<Category> GetAllActiveCategories(Guid siteGuid) {
    return base.Read<Category>(SPNAME_GETALLACTIVE, siteGuid).Result;
}

我觉得一切都很顺利。我已经用async关键字装饰了方法声明。我正在等待异步方法。

我的问题在于,线程阻塞在await myCon.OpenAsync();上。这是我第一次尝试使用async和await,所以我肯定做错了什么,但是不是很明显。请帮帮我!


@KirillShlenskiy 我觉得你说的有道理。我会编辑大部分内容,展示我正在使用的一个阻塞方法。 - fizch
既然您已经发布了消费方面的内容,我很有信心。我会将我的评论重新发布为答案。 - Kirill Shlenskiy
2个回答

4
发布的读取代码没问题。问题在于消费代码。如果您在调用链中调用返回的任务或其前置任务的Wait()Result,很容易出现async死锁。
像这种情况一样,通常的建议是:不要阻塞异步代码。一旦开始使用async/await,就应该在整个调用链中使用async/await
因此,您的调用方法变成了:
public Task<List<Category>> GetAllActiveCategoriesAsync(Guid siteGuid) {
    return base.Read<Category>(SPNAME_GETALLACTIVE, siteGuid);
}

...或者

public async Task<List<Category>> GetAllActiveCategoriesAsync(Guid siteGuid) {
    List<Category> result = await base.Read<Category>(SPNAME_GETALLACTIVE, siteGuid);

    // Do something.

    return result;
}

我的唯一问题是如何在不阻塞它的情况下访问最顶层的结果。 - fizch
1
@fizch,不幸的是,异步编程有一种“污染”你的代码的方式——你必须重写整个链条才能使其成为async。当然,如果你正在使用同步调用链,你可以在你特定的情况下始终使用老式的阻塞方法。或者,你可以通过在Read方法内部等待的所有Task上使用ConfigureAwait(false)来解决问题,但是,虽然这是最佳实践,但故意阻塞Task仍然是一种反模式,应该避免。 - Kirill Shlenskiy
但是我离题了,因为我不再回答原来提出的问题。 - Kirill Shlenskiy

1
罪犯是:

return base.Read<Category>(SPNAME_GETALLACTIVE, siteGuid).Result;

正如Kirill所指出的,任何时候当你在一个任务上使用.Wait()或者.Result方法时,都会同步阻塞。你需要做的是这样的:
public Task<List<Category>> GetAllActiveCategories(Guid siteGuid) {
    return base.Read<Category>(SPNAME_GETALLACTIVE, siteGuid);
}

这将返回一个任务给此方法的调用方法,以此类推...它必须是异步的“一直到最上层”。
如果这段代码的顶级使用者是ASP.NET,那么没问题。只需返回Task<IActionResult>(或包装在任务中的适当返回类型),框架将为您处理等待。
如果您正在编写控制台应用程序或无法使其“始终是异步的”,则必须阻止.Result或使您的方法成为async void并使用await。不幸的是,两者都不是很好的解决方案。异步/等待在某种意义上非常激进,因为您确实需要在整个堆栈中使用它。

@fizch 添加了一些解释。Kirill的回答(和链接)也是不错的信息! - Nate Barbettini

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接