微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

c# – 为什么额外的异步操作使我的代码比没有进行操作时更快?

我正在开发基于短信的游戏(增值服务),其中必须每天向每个用户发送一个问题.有超过500,000个订户,因此性能一个关键因素.由于每个订户可以是具有不同变量的竞争的差异状态,因此在发送文本消息之前必须为每个订户单独查询数据库.为了获得最佳性能,我使用.Net任务并行库(TPL)来生成并行线程池线程,并在每个线程中尽可能多地执行异步操作,以便最终发送文本asap.

在描述实际问题之前,需要更多信息来提供代码.

起初,代码中没有异步操作.我只是使用认任务调度程序将大约500,000个任务安排到Threadpool中,每个任务都将通过例程,阻止所有EF(实体框架)查询并顺序完成其工作.这很好,但还不够快.然后我将所有EF查询更改为Async,结果是速度极佳,但sql服务器中存在如此多的死锁和超时,大约三分之一的订阅者从未收到过文本!在尝试不同的解决方案之后,我决定在24核服务器上运行超过500,000个任务(至少有24个并发线程池线程)时不要执行太多的异步数据库操作!
我回滚了所有更改(Asycn)期望在每个仍然异步的任务中进行一次Web服务调用.

现在奇怪的情况:

在我的代码中,我有一个名为“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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐