c# – 为什么额外的异步操作使我的代码比没有进行操作时更快?
我正在开发基于短信的游戏(增值服务),其中必须每天向每个用户发送一个问题.有超过500,000个订户,因此性能是一个关键因素.由于每个订户可以是具有不同变量的竞争的差异状态,因此在发送文本消息之前必须为每个订户单独查询数据库.为了获得最佳性能,我使用.Net任务并行库(TPL)来生成并行线程池线程,并在每个线程中尽可能多地执行异步操作,以便最终发送文本asap.
在描述实际问题之前,需要更多信息来提供代码. 起初,代码中没有异步操作.我只是使用默认任务调度程序将大约500,000个任务安排到Threadpool中,每个任务都将通过例程,阻止所有EF(实体框架)查询并顺序完成其工作.这很好,但还不够快.然后我将所有EF查询更改为Async,结果是速度极佳,但SQL服务器中存在如此多的死锁和超时,大约三分之一的订阅者从未收到过文本!在尝试不同的解决方案之后,我决定在24核服务器上运行超过500,000个任务(至少有24个并发线程池线程)时不要执行太多的异步数据库操作! 现在奇怪的情况: 在我的代码中,我有一个名为“isCrossSellActive”的布尔变量.设置变量时,会发生更多数据库操作,并且会发生线程等待的asycn webservice调用.当此变量为false时,将不会发生这些操作,包括异步Web服务调用.设置变量时,代码运行速度比没有变量时快得多!似乎由于某种原因,等待的异步代码(协作线程)使代码更快. 这是代码: public async Task AutoSendMessages(...) { //Get list of subscriptions plus some initialization LimitedConcurrencyLevelTaskScheduler lcts = new LimitedConcurrencyLevelTaskScheduler(numberOfThreads); TaskFactory taskFactory = new TaskFactory(lcts); List<Task> tasks = new List<Task>(); //.... foreach (var sub in subscriptions) { AutoSendData data = new AutoSendData { ServiceId = serviceId,MSISDN = sub.subscriber,IsCrossSellActive = bolCrossSellHeader }; tasks.Add(await taskFactory.StartNew(async (x) => { await SendQuestion(x); },data)); } GC.Collect(); try { Task.WaitAll(tasks.ToArray()); } catch (AggregateException ae) { ae.Handle((ex) => { _logRepo.LogException(1,"",ex); return true; }); } await _autoSendRepo.SetAutoSendingStatusEnd(statusId); } public async Task SendQuestion(object data) { //extract variables from input parameter try { if (isCrossSellActive) { int pieceCount = subscriptionRepo.GetSubscriberCarPieces(curSubscription.service,curSubscription.subscriber).Count(c => c.isConfirmed); foreach (var rule in csRules) { if (rule.Applies) { if (await HttpClientHelper.GetJsonAsync<bool>(url,rule.TargetServiceBaseAddress)) { int noOfAddedPieces = SomeCalculations(); if (noOfAddedPieces > 0) { crossSellRepo.SetPromissedPieces(curSubscription.subscriber,curSubscription.service,rule.TargetShortCode,noOfAddedPieces,rule.ExpirationLimitDays); } } } } } // The rest of the code. (Some db CRUD) await SmsClient.SendSoapMessage(subscriber,smsBody); } catch (Exception ex){//...} } 解决方法
好的,多亏了@usr和他给我的线索,问题终于解决了!
他的评论引起我的注意,期待已经等待的taskFactory.StartNew(…)行,它将新任务顺序添加到“任务”列表中,然后由Task.WaitAll(任务)等待; 起初我在taskFactory.StartNew()之前删除了await关键字,它导致代码处于可怕的故障状态!然后我在taskFactory.StartNew()之前返回await关键字并使用断点调试代码,并且令人惊讶地看到线程在第一个线程到达“SendQuestion”例程内的第一个线程之前一个接一个地运行.当设置“isCrossSellActive”标志时,尽管线程应该执行更多作业,但是先前达到第一个await关键字,从而启用下一个计划任务.但是当它没有设置时,唯一的await关键字是例程的最后一行,所以它最有可能顺序运行到最后. usr建议在for循环中删除await关键字似乎是正确的但问题是Task.WaitAll()行会在错误的Task列表上等待< Task< void>>而不是任务< void>.我终于使用Task.Run而不是TaskFactory.StartNew,一切都改变了.现在服务运作良好. for循环中的最终代码是: tasks.Add(Task.Run(async () => { await SendQuestion(data); })); 问题解决了. 附:阅读关于Task.Run的这篇文章以及为什么TaskFactory.StartNew是危险的:http://blog.stephencleary.com/2013/08/startnew-is-dangerous.html (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- database – 表的维护:在截断和重新填充后,是否
- cocos2dx使用通知节点NotificationNode的注意事项
- ajax中执行服务器返回的js脚本
- iphone – 从JSON到NSArray
- Vue异步组件处理路由组件加载状态的解决方案
- 生成cocos2dx 2.2.6 TestCpp例子 android工程遇到
- ruby-on-rails – Rails教程:SQLite3 :: Constr
- ruby-on-rails – Ruby On Rails – 重用错误消息
- Cocos2d-x 3.x利用Socket创建客户端和服务端
- c – 为LLVM项目采样CMakeLists.txt文件