简体   繁体   English

C#ASP .NET:如何将代码转换为更多并发?

[英]C# ASP .NET: How to transform code to more concurrent?

I'm making auction web application using c# asp .net. 我正在使用c#asp .net开发拍卖Web应用程序。

My web application got 2 methods that I must synchronize, can't let them invoke at the same time, because they could make conflicts in my database, so I've used only lock(object) code blocks to protect these 2 methods. 我的Web应用程序有2个必须同步的方法,不能让它们同时调用,因为它们可能在数据库中引起冲突,因此我仅使用了锁(对象)代码块来保护这2个方法。

I need to make this more concurrent, because this solution locking full methods makes application very slow at some moments. 我需要使其更加并发,因为这种锁定完整方法的解决方案有时会使应用程序非常慢。

I've used SignalR library to send notifications between server and clients, and MyRegistry class to make timer that ticks every second and send notifications to online clients. 我已经使用SignalR库在服务器和客户端之间发送通知,并使用MyRegistry类创建了一个计时器,它每秒钟滴答一次,并将通知发送到在线客户端。

In my opinion lines of code with access to database, and signalR methods for sending notifications are those lines that maybe slows system. 在我看来,可以访问数据库的代码行以及用于发送通知的signalR方法就是那些可能会使系统变慢的行。

Here's my object that's only used as a lock object: 这是我的对象,仅用作锁定对象:

public static class mutex
{
    public static string lockObject = "MutEx";
}

My Hub (SignalR) 我的中心(SignalR)

public class MyHub : Hub
{

    private static Dictionary<string, string> hashUsersConnIds = new Dictionary<string, string>(512);
    private IEP_Model db = new IEP_Model();
    public readonly log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    // Server Hub
    public void Send(long IDAuc, string lastBidderEmail)
    {

        AspNetUser user = db.AspNetUsers.SingleOrDefault(r => r.Email == lastBidderEmail);

        lock (mutex.lockObject)
        {

            auction auction = db.auctions.SingleOrDefault(a => a.IDAuc == IDAuc);
            long productNewPrice = auction.price + auction.increment + 1;

            // User can bid!
            if ((productNewPrice <= user.TokenNumber))
            {
                long newBidderCount = user.TokenNumber - productNewPrice;
                user.TokenNumber = newBidderCount;
                db.Entry(user).State = EntityState.Modified;

                bid newBid = new bid();
                newBid.Id = user.Id;
                newBid.IDAuc = auction.IDAuc;
                newBid.bidTime = DateTime.Now;
                newBid.tokens = productNewPrice;
                db.bids.Add(newBid);

                // Return tokens to previous client
                var previousBidder =    from o
                                        in db.UserAuctionInvested
                                        where o.AuctionId == auction.IDAuc
                                        select o.UserId;

                string previousBidderID = previousBidder.SingleOrDefault();
                AspNetUser previous = db.AspNetUsers.Find(previousBidderID);

                // Previous bidder exists
                if (previousBidderID != null)
                {
                    long tokensToReturn = (long)db.UserAuctionInvested.Where(u => u.AuctionId == auction.IDAuc && u.UserId == previousBidderID).SingleOrDefault().TokenInvested;
                    long newTokenCount = previous.TokenNumber + tokensToReturn;
                    previous.TokenNumber = newTokenCount;

                    // PUSH NOTIFICATION: SET PREVIOUS CLIENT'S TOKEN NUMBER
                    var clientSelector = "token" + previous.Email.Replace("@", "_");
                    var clientAlertSelector = "alertToken" + previous.Email.Replace("@", "_");
                    var warningAlertSelector = "warning" + previous.Email.Replace("@", "_");
                    // PREVIOUS CLIENT IS ONLINE
                    if (hashUsersConnIds.ContainsKey(previous.Email) && previous.Email != lastBidderEmail)
                    {
                        Clients.Client(hashUsersConnIds[previous.Email]).setTokenNumber(clientSelector, newTokenCount, clientAlertSelector, warningAlertSelector, auction.product_name);
                    }
                    // PUSH NOTIFICATION: CURRENT CLIENT (LAST BIDDER) NEW TOKEN COUNT
                    clientSelector = "token" + lastBidderEmail.Replace("@", "_");
                    clientAlertSelector = "alertToken" + lastBidderEmail.Replace("@", "_");
                    warningAlertSelector = "warning" + previous.Email.Replace("@", "_");
                    if (previous.Email != lastBidderEmail && hashUsersConnIds.ContainsKey(lastBidderEmail)) {
                        Clients.Client(hashUsersConnIds[lastBidderEmail]).setTokenNumber(clientSelector, newBidderCount, clientAlertSelector, "x", "x");
                    }
                    db.Entry(previous).State = EntityState.Modified;
                    UserAuctionInvested uaiPrevious = db.UserAuctionInvested.Find(previousBidderID, auction.IDAuc);
                    db.UserAuctionInvested.Remove(uaiPrevious);
                } 
                // This client is first bidder
                else
                {
                    var clientSelector = "token" + lastBidderEmail.Replace("@", "_");
                    var clientAlertSelector = "alertToken" + lastBidderEmail.Replace("@", "_");
                    // PUSH NOTIFICATION: CURRENT CLIENT (LAST BIDDER) NEW TOKEN COUNT
                    if (hashUsersConnIds.ContainsKey(lastBidderEmail))
                    {
                        Clients.Client(hashUsersConnIds[lastBidderEmail]).setTokenNumber(clientSelector, newBidderCount, clientAlertSelector, "x", "x");
                    }
                }

                // Creating new bid userAuctionInvested -> [dbo].[userAuctionInvested] (user, auction, tokenNo)
                UserAuctionInvested uai = new UserAuctionInvested();
                uai.AuctionId = auction.IDAuc;
                uai.UserId = user.Id;
                uai.TokenInvested = productNewPrice;
                db.UserAuctionInvested.Add(uai);

                // Update Auction
                auction.increment += 1;
                auction.lastbidder = lastBidderEmail;

                // Seconds to end of auction
                var secondsDifference = ((DateTime)auction.close_date_time - DateTime.Now).TotalSeconds;
                if (secondsDifference <= 10)
                {
                    DateTime oldCloseDateTime = (DateTime)auction.close_date_time;
                    DateTime newCloseDateTime = oldCloseDateTime.AddSeconds(10);
                    auction.close_date_time = newCloseDateTime;
                    auction.duration += 10;
                }

                db.Entry(auction).State = EntityState.Modified;
                string remainingToEnd = ((DateTime)auction.close_date_time - DateTime.Now).ToString(@"dd\:hh\:mm\:ss");
                Clients.All.clientBidsUpdate(IDAuc, auction.state, remainingToEnd, lastBidderEmail, auction.price + auction.increment, "false");
                // Update details auction page // clientWarningSelector, auctionNameWarning
                Clients.All.auctionDetailsUpdate(IDAuc, lastBidderEmail, auction.price + auction.increment, newBid.bidTime.ToString(@"dd\:hh\:mm\:ss"), "Open");
                db.SaveChanges();
                return;

            }
            // Client was previous bidder needs to pay +1 on actual price
            else if (auction.lastbidder == user.Email)
            {
                if (user.TokenNumber > 0) // can place next bid
                {
                    user.TokenNumber = user.TokenNumber - 1;
                    db.Entry(user).State = EntityState.Modified;
                    bid newBid = new bid();
                    newBid.Id = user.Id;
                    newBid.IDAuc = auction.IDAuc;
                    newBid.bidTime = DateTime.Now;
                    newBid.tokens = auction.price + auction.increment + 1;
                    db.bids.Add(newBid);
                    if (hashUsersConnIds.ContainsKey(lastBidderEmail))
                    {
                        var clientSelector = "token" + lastBidderEmail.Replace("@", "_");
                        var clientAlertSelector = "alertToken" + lastBidderEmail.Replace("@", "_");
                        var clientWarningSelector = "warning" + lastBidderEmail.Replace("@", "_");
                        Clients.Client(hashUsersConnIds[lastBidderEmail]).setTokenNumber(clientSelector, user.TokenNumber, clientAlertSelector, "x", "x");
                    }
                    // Updating userAuctionInvested -> [dbo].[userAuctionInvested] (user, auction, tokenNo)
                    UserAuctionInvested uai = db.UserAuctionInvested.Where(u => u.AuctionId == auction.IDAuc && u.UserId == user.Id).SingleOrDefault();
                    uai.TokenInvested += 1;
                    db.Entry(uai).State = EntityState.Modified;
                    // Update Auction
                    auction.increment += 1;
                    // Seconds to end of auction
                    var secondsDifference = ((DateTime)auction.close_date_time - DateTime.Now).TotalSeconds;
                    if (secondsDifference <= 10)
                    {
                        DateTime oldCloseDateTime = (DateTime)auction.close_date_time;
                        DateTime newCloseDateTime = oldCloseDateTime.AddSeconds(10);
                        auction.close_date_time = newCloseDateTime;
                        auction.duration += 10;
                    }
                    db.Entry(auction).State = EntityState.Modified;
                    string remainingToEnd = ((DateTime)auction.close_date_time - DateTime.Now).ToString(@"dd\:hh\:mm\:ss");
                    Clients.All.clientBidsUpdate(IDAuc, auction.state, remainingToEnd, lastBidderEmail, auction.price + auction.increment, "false");
                    Clients.All.auctionDetailsUpdate(IDAuc, lastBidderEmail, auction.price + auction.increment, newBid.bidTime.ToString(@"dd\:hh\:mm\:ss"), "Open");

                    db.SaveChanges();
                    return;
                }
            }
            // No tokens - Warn user!
            string remaining = ((DateTime)auction.close_date_time - DateTime.Now).ToString(@"dd\:hh\:mm\:ss");
            Clients.All.clientBidsUpdate(IDAuc, auction.state, remaining, lastBidderEmail, auction.price + auction.increment, "true");
        }
    }

    // Registring client
    public void registerConId(string email)
    {
        hashUsersConnIds[email] = Context.ConnectionId;
    }

}

And finally here's my Registry class that acts like a Timer on my web application, ticks every one seconds and updates database: 最后,这是我的Registry类,它像Web应用程序上的Timer一样,每隔一秒滴答一次并更新数据库:

public class MyRegistry : Registry
{

    public readonly log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    public MyRegistry()
    {

        Schedule(() =>
        {

            // Check if some auction is over each 1 sec
            lock (mutex.lockObject)
            {

                IEP_Model db = new IEP_Model();
                var hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();

                var auctions = from o 
                               in db.auctions
                               where o.state == "Open"
                               select o;

                var auctionsList = auctions.ToList();

                foreach (var auction in auctionsList)
                {

                    DateTime now = DateTime.Now;
                    DateTime end = (DateTime)auction.close_date_time;

                    // Time is up! Auction is "Expired" or "Sold"
                    if (now >= end)
                    {
                        var edited = db.auctions.Find(auction.IDAuc);
                        // NO Winner
                        if (edited.increment == 0)
                        {
                            edited.state = "Expired";
                            db.Entry(edited).State = EntityState.Modified;
                            TimeSpan timeEnd = new TimeSpan(0);
                            string newDurationExpired = timeEnd.ToString(@"dd\:hh\:mm\:ss");

                            hubContext.Clients.All.timerUpdate(auction.IDAuc, edited.state, newDurationExpired, " - ", edited.price, "false", "odd", "true");
                            db.SaveChanges();
                        }
                        // YES Winner
                        else
                        {
                            edited.state = "Sold";
                            db.Entry(edited).State = EntityState.Modified;
                            db.SaveChanges();

                            // Refresh client
                            long soldPrice = edited.price + edited.increment;
                            TimeSpan timeEnd = new TimeSpan(0);
                            string newDurationSold = timeEnd.ToString(@"dd\:hh\:mm\:ss");
                            hubContext.Clients.All.timerUpdate(auction.IDAuc, edited.state, newDurationSold, edited.lastbidder, soldPrice, "false", "odd", "true");

                            // Return tokens to non-winners
                            string winnerId = db.AspNetUsers.Where(a => a.Email == edited.lastbidder).SingleOrDefault().Id;
                            //var bids = db.bids.Where(b => b.IDAuc == auction.IDAuc && b.Id != winnerId).ToList();
                            var userInvested = db.UserAuctionInvested.Where(i => i.AuctionId == auction.IDAuc);

                            foreach (var item in userInvested.ToList())
                            {
                                if (item.UserId != winnerId) // Not winner - return money
                                {
                                    AspNetUser FetchUser = db.AspNetUsers.Find(item.UserId);
                                    FetchUser.TokenNumber += (long)item.TokenInvested;
                                    db.Entry(FetchUser).State = EntityState.Modified;
                                }
                                db.UserAuctionInvested.Remove(item);
                            }

                            db.SaveChanges();

                        }
                    }

                    // Update auction - is still active
                    long actualPrice = auction.price + auction.increment;
                    TimeSpan difference = end.Subtract(now);
                    string newDuration = difference.ToString(@"dd\:hh\:mm\:ss");

                    string lastTenSeconds__ = "false";
                    string numberOddEven__ = "odd";
                    string end__ = "false";
                    if (difference.TotalSeconds <= 10)
                    {
                        lastTenSeconds__ = "true";
                        if (difference.TotalSeconds <= 1)
                        {
                            end__ = "true";
                        }
                        if ((int)Math.Ceiling(difference.TotalSeconds) % 2 == 0) 
                        {
                            numberOddEven__ = "even";
                        }
                    }
                    hubContext.Clients.All.timerUpdate(auction.IDAuc, auction.state, newDuration, auction.lastbidder, actualPrice, lastTenSeconds__, numberOddEven__, end__);


                }

            }
        }).ToRunNow().AndEvery(1).Seconds();
    }

}

I'd need a help to get an idea how could I transform a bit this code, so I could make it more concurrent. 我需要一个帮助来了解如何对这段代码进行一些转换,以便使其并发。 I've seen that c# also supports Monitors, wondering if I would benefit from using it? 我已经看到c#也支持Monitors,想知道我是否可以从中受益?

Looking at your code, the first thing I would suggest is reducing your db calls to be a few as possible, front load your current auctions, logged in users, and anything else that is fairly static, and either cache them using something like Redis (in memory caching tool) or in dictionaries. 查看您的代码,我建议的第一件事是将您的数据库调用尽可能减少,预先加载当前的拍卖,登录的用户以及相当静态的所有其他内容,然后使用Redis(在内存缓存工具中)或字典中。 (for the binary search performance) (用于二进制搜索性能)

Secondly I would suggest a multi-threaded process that uses a ConcurrentDictionary for your live auction tracking, it has the speed you are looking for, and you can wrap it on a BlockingCollection if you need the thread to wait for changes. 其次,我建议使用ConcurrentDictionary进行实时拍卖跟踪的多线程进程,它具有所需的速度,如果需要线程等待更改,可以将其包装在BlockingCollection上。 That way you do not have to use locks. 这样,您不必使用锁。 Also you can off load persisting the data to storage using a Queue. 此外,您还可以使用队列减轻将数据持久存储到存储中的负担。

If you have Pluralsight I would suggest this course: 如果您有Pluralsight,我建议您参加以下课程:

https://app.pluralsight.com/library/courses/csharp-concurrent-collections/table-of-contents https://app.pluralsight.com/library/courses/csharp-concurrent-collections/table-of-contents

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM