我正在研究一个具有以下详细信息的项目:
以下是许多Web服务方法之一的最简单示例:
public async Task<int> Count()
{
int result = 0;
//There is nothing to await here, so I use Task.Run
await Task.Run(() =>
{
using (IDB ctx = new DB())
{
result = ctx.Customers.All.Count();
}
//Here could happen a lot more
//System.Threading.Thread.Sleep(10000);
}).ConfigureAwait(false);
return result;
}
如您所见,我正在使用Task.Run访问某些数据,因为没有任何存储库接口提供异步方法。我等不及了。如果我想进行“真正的异步”操作,则必须重写完整的存储库界面。
如果我不使用Task.Run,则服务器将阻止所有其他传入请求。
我的2个问题是:
使用Task.Run在这种情况下有什么问题吗?
即使它可以工作并且可能不是完全错误,是否有更好,更专业的解决方案来在异步方法中调用同步代码?
这个问题的最初原因是,我读到,在异步方法中使用Task.Run是“伪异步” 。 (我认为Task.Run将启动一个新线程,而“真正的异步” 代码则不会)
我回答了自己的问题,请参见下面的答案。我希望它可以帮助其他人。
答案 0 :(得分:1)
是的,它是伪造的async
,并且在启动线程并阻塞线程时可扩展性较低,直到完成后才将其退还。
但是
我称这类方法为“伪异步方法”,因为它们看起来 异步,但实际上只是通过同步工作来伪造它 在后台线程上。通常,请勿在 该方法的实施;而是使用Task.Run调用 方法。此准则有两个原因:
- 您的代码的使用者假定,如果一个方法具有异步签名,那么它将真正地异步执行。伪装 通过在后台线程上进行同步工作来实现异步 是令人惊讶的行为。
- 如果您的代码曾经在ASP.NET上使用过,则伪同步方法会导致开发人员走错路径。服务器上异步的目标 方面是可伸缩性,伪异步方法的可伸缩性较差 不仅仅是使用同步方法。
公开“异步于同步”包装的想法也是一个非常重要的想法。 滑坡,这可能会导致 在同步和异步中公开单个方法 形式。许多问我这种做法的人是 考虑为长时间运行的CPU暴露异步包装器 操作。目的是好的:帮助响应。 但是,如前所述,响应可以通过以下方式轻松实现 API的使用者,而使用者实际上可以在适当的位置这样做 级别的矮胖,而不是针对每个健谈的单个操作。 此外,定义哪些操作可以长时间运行是 出奇的困难。很多方法的时间复杂度往往 差异很大。
但是,您实际上实际上不适合这两个类别。根据您的描述,您正在托管此WCF服务。如果正确设置了InstanceContextMode
和ConcurrencyMode
,它将始终异步运行代码。假设您使用适当的设置生成了代理,您还可以为客户端的呼叫运行TBA包装。
如果我对您的理解正确,则可以让此方法完全同步,让 WCF 处理细节并节省资源
更新
一个例子:如果我在任何Web服务方法中使用Task.Run,我可以 甚至在Task.Run内调用Thread.Sleep(10000),服务器仍在 响应任何传入流量。
我认为以下内容可能对您最有帮助
Sessions, Instancing, and Concurrency
会话是两个端点之间发送的所有消息的关联。 实例化是指控制用户定义服务的生存期 对象及其相关的InstanceContext对象。并发是 控制在一个线程中执行的线程数的术语 InstanceContext在同一时间。
似乎您的WCF服务已为InstanceContextMode.PerSession
和ConcurrencyMode.Single
设置。如果您的服务是无状态的,则可能要使用InstanceContextMode.PerCall
并仅在有真正可以等待的内容时使用async
答案 1 :(得分:1)
首先:谢谢大家的提示。我需要他们更深入地研究问题。
我找到了解决此问题的真正方法,我认为,我可以通过详细回答自己的问题来为社区增加一些价值。
解决方案也可以在这篇很棒的文章中找到:https://www.oreilly.com/library/view/learning-wcf/9780596101626/ch04s04.html
以下是初始问题和解决方案的快速摘要:
目标
问题(和错误的解决方案)(我的第一个问题)
解决方案(在文章中找到)
解决方案1(经过测试,可以正常工作-不再受阻)
解决方案2(经过测试,可以正常工作-不再受阻)
代码:
private List<ServiceHost> _ServiceHosts = new List<ServiceHost>();
private List<Thread> _Threads = new List<Thread>();
foreach (ServiceHost host in _ServiceHosts)
{
_Threads.Add(new Thread(() => { host.Open(); }));
_Threads[_Threads.Count - 1].IsBackground = true;
_Threads[_Threads.Count - 1].Start();
}
解决方案3(未经测试,但已在文章中提及)
我希望这可以帮助其他遇到相同问题的人。