我正在进行一个项目,其中包含以下详细信息:
- 不涉及IIS或其他第三方网络服务器
- .NET 4.5 Windows应用程序,充当"服务器">
- 服务器启动多个WCF Web服务,所有这些服务都是自托管的
- 多个用户可以访问这些Web服务
以下是众多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,服务器将阻止所有其他传入请求。
我的两个问题是:
在这种情况下使用Task.Run有什么问题吗?
即使它是有效的,而且可能不是完全错误的,有没有更好、更专业的解决方案来调用异步方法中的同步代码?
我读到,这个问题的最初原因是,在异步方法中使用Task.Run是"伪异步"。(我认为Task.Run会启动一个新线程,而"real-async">代码则不会)
我回答了我自己的问题,见下面的答案。我希望它能帮助其他人。
是的,它是假的async
,当它启动线程并阻止它时,可伸缩性较差,在它完成之前不会返回。
然而,
- 正如Stephen在他的任务中所暗示的那样。跑步礼仪和正确使用
我称这种方法为"伪异步方法",因为它们看起来异步,但实际上只是通过做同步工作来假装在后台线程上。通常,不要在方法的实施;相反,请使用Task.Run调用方法该指南有两个原因:
- 代码的使用者认为,如果一个方法具有异步签名,那么它将真正异步地执行。伪造通过在后台线程上执行同步工作实现异步性是令人惊讶的行为
- 如果您的代码曾经在ASP.NET上使用过,那么伪异步方法会导致开发人员走上错误的道路。服务器上异步的目标一方面是可伸缩性,而伪异步方法的可伸缩性较差而不仅仅是使用同步方法
- 还有Stephen Toub(又名平行先生)我应该为同步方法公开异步包装器吗
公开"async over sync"包装器的想法也是滑到极点可能导致同时在同步和异步中公开的单个方法表格。很多问我这种做法的人考虑为长时间运行的CPU绑定公开异步包装器操作。这样做的目的很好:帮助做出反应。但正如所指出的,响应能力可以很容易地通过API的消费者,而消费者实际上可以在右边这样做厚重的程度,而不是每个健谈的单独操作。此外,定义哪些操作可能是长时间运行的令人惊讶的困难。许多方法的时间复杂性往往差异很大。
然而,实际上你真的不属于这两类。根据您的描述,您正在托管此WCF服务。如果您已正确设置了InstanceContextMode
和ConcurrencyMode
,它将异步运行您的代码。假设您使用适当的设置生成了代理,那么您还可以从客户端为您的呼叫运行TBA包装器。
如果我理解正确的话,你可以让这个方法完全同步,让WCF处理细节并节省资源
更新
一个例子:如果我在任何webservice方法中使用Task.Run,我可以甚至在Task.Run中调用Thread.Sleep(10000),服务器就会保持响应于任何传入业务。
我认为以下内容可能对您的有最大帮助
会话、实例化和并发
会话是在两个端点之间发送的所有消息的相关性。实例化是指控制用户定义服务的生存期对象及其相关的InstanceContext对象。并发是用于控制在InstanceContext。
您的WCF服务似乎是为InstanceContextMode.PerSession
和ConcurrencyMode.Single
设置的。如果您的服务是无状态的,那么您可能希望使用InstanceContextMode.PerCall
,并且只有在您有真正可以等待的时才使用async
首先:感谢大家的提示。我需要他们深入研究这个问题。
我已经找到了这个问题的真正解决方案,我想,通过详细回答我自己的问题,我可以为社区增加一些价值。
解决方案也可以在这篇伟大的文章中找到:https://www.oreilly.com/library/view/learning-wcf/9780596101626/ch04s04.html
以下是对初始问题和解决方案的快速总结:
目标
- 我的目标是在.NET 4.5应用程序中托管多个自托管WCF服务
- 多个客户端都可以访问所有自托管WCF服务
- 当多个用户正在使用所有自托管WCF服务时,它们不得相互阻止
问题(和错误的解决方案)(我的第一个问题)
- 我的问题是,每当一个客户端使用Web服务时,它都会阻止其他Web服务,直到它返回到客户端
- 我使用哪种InstanceContextMode或ConcurrentMode并不重要
- 我的错误解决方案是使用async和Task.Run("Fake-async")。它起了作用,但不是真正的解决方案
解决方案(在文章中找到)
- 当自托管WCF Web服务时,必须确保始终调用ServiceHost.在不同于UI线程的单独线程中打开
- 无论何时在控制台、WinForms或WPF应用程序或Windows服务中打开ServiceHost,您都必须注意,何时调用ServiceHost.open以及如何使用ServiceBehaviorAttribute.UseSynchronizationContext
- ServiceBehaviorAttribute.UseSynchronizationContext的默认值为True。(这很糟糕,会导致阻塞!)
- 如果您只调用ServiceHost.Open,而不将UseSynchronizationContext设置为false,则所有ServiceHost都将在UI线程中运行,并阻止此线程和其他线程
解决方案1(经过测试且有效-不再阻塞)
- 设置ServiceBehaviorAttribute.UseSynchronizationContext=false
解决方案2(经过测试且有效-不再阻塞)
- 不要触摸ServiceBehaviorAttribute.UseSynchronizationContext,让它成为真的
- 但是至少创建一个或多个线程,在其中调用ServiceHost。Open
代码:
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(未测试,但在文章中提到)
- 不要触摸ServiceBehaviorAttribute.UseSynchronizationContext,让它成为真的
- 但请确保,在创建UI线程之前调用ServiceHost.Open
- 然后ServiceHosts将使用不同的线程,而不会阻止UI线程
我希望这能帮助其他有同样问题的人。