异步Task.Run在自托管的Web服务中运行-只要有效,它是否“使用不正确”?

时间:2018-10-20 06:06:28

标签: c# web-services asynchronous server task

我正在研究一个具有以下详细信息的项目:

  • 不涉及IIS或其他第三方Web服务器
  • .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,​​则服务器将阻止所有其他传入请求。

我的2个问题是:

  1. 使用Task.Run在这种情况下有什么问题吗?

  2. 即使它可以工作并且可能不是完全错误,是否有更好,更专业的解决方案来在异步方法中调用同步代码?

这个问题的最初原因是,我读到,在异步方法中使用Task.Run是“伪异步” 。 (我认为Task.Run将启动一个新线程,而“真正的异步” 代码则不会)

我回答了自己的问题,请参见下面的答案。我希望它可以帮助其他人。

2 个答案:

答案 0 :(得分:1)

是的,它是伪造的async,并且在启动线程并阻塞线程时可扩展性较低,直到完成后才将其退还。

但是

  

我称这类方法为“伪异步方法”,因为它们看起来   异步,但实际上只是通过同步工作来伪造它   在后台线程上。通常,请勿在   该方法的实施;而是使用Task.Run调用   方法。此准则有两个原因:

     
      
  • 您的代码的使用者假定,如果一个方法具有异步签名,那么它将真正地异步执行。伪装   通过在后台线程上进行同步工作来实现异步   是令人惊讶的行为。
  •   
  • 如果您的代码曾经在ASP.NET上使用过,则伪同步方法会导致开发人员走错路径。服务器上异步的目标   方面是可伸缩性,伪异步方法的可伸缩性较差   不仅仅是使用同步方法。
  •   
  

公开“异步于同步”包装的想法也是一个非常重要的想法。   滑坡,这可能会导致   在同步和异步中公开单个方法   形式。许多问我这种做法的人是   考虑为长时间运行的CPU暴露异步包装器   操作。目的是好的:帮助响应。   但是,如前所述,响应可以通过以下方式轻松实现   API的使用者,而使用者实际上可以在适当的位置这样做   级别的矮胖,而不是针对每个健谈的单个操作。   此外,定义哪些操作可以长时间运行是   出奇的困难。很多方法的时间复杂度往往   差异很大。

但是,您实际上实际上不适合这两个类别。根据您的描述,您正在托管此WCF服务。如果正确设置了InstanceContextModeConcurrencyMode,它将始终异步运行代码。假设您使用适当的设置生成了代理,您还可以为客户端的呼叫运行TBA包装。

如果我对您的理解正确,则可以让此方法完全同步,让 WCF 处理细节并节省资源

更新

  

一个例子:如果我在任何Web服务方法中使用Task.Run,​​我可以   甚至在Task.Run内调用Thread.Sleep(10000),服务器仍在   响应任何传入流量。

我认为以下内容可能对您最有帮助

Sessions, Instancing, and Concurrency

  

会话是两个端点之间发送的所有消息的关联。   实例化是指控制用户定义服务的生存期   对象及其相关的InstanceContext对象。并发是   控制在一个线程中执行的线程数的术语   InstanceContext在同一时间。

似乎您的WCF服务已为InstanceContextMode.PerSessionConcurrencyMode.Single设置。如果您的服务是无状态的,则可能要使用InstanceContextMode.PerCall并仅在有真正可以等待的内容时使用async

答案 1 :(得分:1)

首先:谢谢大家的提示。我需要他们更深入地研究问题。

我找到了解决此问题的真正方法,我认为,我可以通过详细回答自己的问题来为社区增加一些价值。

解决方案也可以在这篇很棒的文章中找到:https://www.oreilly.com/library/view/learning-wcf/9780596101626/ch04s04.html

以下是初始问题和解决方案的快速摘要:

目标

  • 我的目标是在.NET 4.5应用程序中托管多个自托管WCF服务
  • 所有自托管WCF服务都可供多个客户端访问
  • 当多个用户正在使用它们时,所有自托管WCF服务都不得相互阻塞

问题(和错误的解决方案)(我的第一个问题)

  • 我的问题是,每当一个客户端使用Web服务时,它将阻止其他Web服务,直到返回给客户端
  • 我使用哪种InstanceContextMode或ConcurrencyMode都没关系
  • 我错误的解决方案是使用async和Task.Run(“伪异步”)。它起作用了,但这不是真正的解决方案。

解决方案(在文章中找到)

  • 在自托管WCF Web服务时,必须确保始终调用ServiceHost.Open,它位于与UI线程不同的单独线程中。
  • 每当在控制台,WinForms或WPF应用程序或Windows Service中打开ServiceHost时,都必须知道何时调用ServiceHost.Open以及如何使用ServiceBehaviorAttribute.UseSynchronizationContext
  • ServiceBehaviorAttribute.UseSynchronizationContext的默认值为True。 (这很糟糕,会导致阻塞!)
  • 如果仅调用ServiceHost.Open,而不设置UseSynchronizationContext = false,则所有ServiceHosts都将在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,请使其为真
  • 但是请确保您调用ServiceHost。在创建UI线程之前打开
  • 然后ServiceHosts将使用其他线程,而不阻塞UI线程

我希望这可以帮助其他遇到相同问题的人。