C# 并行线程中的取消令牌

C# 并行线程中的取消令牌,c#,multithreading,task-parallel-library,cancellationtokensource,C#,Multithreading,Task Parallel Library,Cancellationtokensource,我发布这篇文章部分是出于对任务并行库工作原理的兴趣,也是为了传播知识。此外,还可以调查我的“取消”更新是否是导致用户突然注销的新问题的原因 我正在从事的项目包含以下组件: 网站表单。作为管理公司车辆门户的网站。进一步称为“网络” WCF web服务。独立计算机上的后端服务。进一步称为“服务” 第三方服务。进一步称为“第三” 注意:我使用的是.NET4.0。因此,任务并行库的更新不可用 分配给我解决的问题是,登录功能非常慢,占用大量CPU。这后来被承认是第三方服务中的一个问题。然而,我尽力优

我发布这篇文章部分是出于对任务并行库工作原理的兴趣,也是为了传播知识。此外,还可以调查我的“取消”更新是否是导致用户突然注销的新问题的原因

我正在从事的项目包含以下组件:

  • 网站表单。作为管理公司车辆门户的网站。进一步称为“网络”
  • WCF web服务。独立计算机上的后端服务。进一步称为“服务”
  • 第三方服务。进一步称为“第三”
注意:我使用的是.NET4.0。因此,任务并行库的更新不可用


分配给我解决的问题是,登录功能非常慢,占用大量CPU。这后来被承认是第三方服务中的一个问题。然而,我尽力优化登录行为。

登录请求和响应不包含相应的大量数据。但是为了收集响应数据,需要对第三方服务进行几个API调用

1.变更前 Web调用服务上的WCF方法来收集“会话数据”。 这个方法有时会花费很长的时间,以至于超时(我认为超时设置为1分钟)

“GetSessionData”方法的伪表示:

有关代码段,请参见

用户拥有越多的
协议
帐户
,这种方法很快就会变得很重

2.并行循环 我想我可以用一个
Parallel.foreach()
替换
foreach
循环。对于较大的用户,这大大提高了该方法的速度

有关代码段,请参见

3.取消 我们遇到的另一个问题是,当web服务服务器的CPU使用率达到最大值时,所有方法调用都会变得非常慢,并可能导致用户超时。对超时的一种常见响应是重试,因此用户触发另一次登录尝试,由于CPU使用率较高,该尝试处于“排队”(?)状态。这是在第一个请求尚未返回时发生的

我们发现,如果网站超时,请求仍然有效。所以我们决定在服务端实现一个类似的超时

有关代码段,请参见

其思想是使用CancellationToken调用GetSessionData(..),该令牌将在与Web超时时间大致相同的时间后触发Cancel。因此,如果没有人在那里展示或使用结果,就不会进行任何工作

我还实现了对第三方服务的方法调用的取消

为所有循环和服务调用共享相同的CancellationToken是否正确?当所有线程都通过抛出取消异常“中止”时,是否会出现问题?

有关代码段,请参见

为所有循环和服务调用共享相同的CancellationToken是否正确?当所有线程都通过抛出cancel异常“中止”时,是否会出现问题

是的,没错。是的,同时抛出大量异常可能会有问题,但只能在特定情况下和大量并行工作中

几点提示:

  • 每个完整操作使用一个CancellationTokenSource。例如,每个请求。将相同的取消令牌从此源传递给每个异步方法
  • 您可以避免抛出异常,而只从方法返回。稍后,要检查工作是否已完成且未取消任何内容,请检查cts上的IsCancellationRequested
  • 在每次迭代中检查循环内的取消标记,如果取消,则返回
  • 仅当存在IO工作时使用线程,例如,当您从数据库查询某些内容或向其他服务请求时;不要将其用于CPU受限的工作
工作日结束时我很累,建议做一件坏事。主要来说,IO绑定工作不需要线程,例如,等待来自第三个服务的数据库的响应。仅将线程用于CPU计算

此外,我再次检查了您的代码,发现了几个瓶颈:

  • 您可以异步调用GetAgreementDetail、GetFuelCards、GetServiceLevel、GetCustomers;不要等待下一个,不要运行所有四个请求
  • 您也可以并行调用GetAddressByCustomer和GetBranchs
  • 我注意到你使用互斥。我想这是为了保护协议。客户和响应。客户添加。如果是这样,您可以缩小锁的范围
  • 您可以更早地开始使用车辆,因为您在方法开始时就知道UserId;同时也要这样做

  • 如果获取数据超时超过1分钟,则加载的信息肯定过多。我在425个不同的表上加载了36000多条记录,而且不超过7秒。只加载必要的内容,这将在这一点上有所帮助。您的循环很可能被替换为内部连接。@Franck问题是我依赖于第三方服务,目前(预补丁)在压力下速度很慢。我可以重构网站,只在需要的时候询问信息,但这需要对当前的实现进行一次大的检修。现在,我们正在寻找一个快速解决方案来修复性能issues@LazyTarget更新了我的回答并提出了几点建议:分析您的通话,找出真正的性能瓶颈。使用O(1)查找集合-即使用
    字典
    哈希集
    来确定您是否已看到特定对象,而不是使用LINQ。限制
    Parallel.ForEach
    的并行度,以避免它在遇到阻塞调用(即
    lock
    Task.Wait
    或外部服务IO)时连续创建新线程,因为这将使线程池饱和。
    var agreements = getAgreements(request);
    foreach (var agreement in agreements)
    {
        getAgreementDetails(agreement);
        var customers = getCustomersWithAgreement(agreement);
        foreach (var customer in customers)
        {
            getCustomerInfo(customer);
            getCustomerAddress(customer);
            getCustomerBranches(customer);
        }
    }
    
    var person = getPerson(request);
    var accounts = getAccount(person.Id);
    foreach (var account in accounts)
    {
        var accountDetail = getAccountDetail(account.Id);
        foreach (var vehicle in accountDetail.Vehicles)
        {
            getCurrentMilageReport(vehicle.Id);
        }
    }
    return sessionData;