繁体   English   中英

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

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

我正在使用c#asp .net开发拍卖Web应用程序。

我的Web应用程序有2个必须同步的方法,不能让它们同时调用,因为它们可能在数据库中引起冲突,因此我仅使用了锁(对象)代码块来保护这2个方法。

我需要使其更加并发,因为这种锁定完整方法的解决方案有时会使应用程序非常慢。

我已经使用SignalR库在服务器和客户端之间发送通知,并使用MyRegistry类创建了一个计时器,它每秒钟滴答一次,并将通知发送到在线客户端。

在我看来,可以访问数据库的代码行以及用于发送通知的signalR方法就是那些可能会使系统变慢的行。

这是我的对象,仅用作锁定对象:

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

我的中心(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;
    }

}

最后,这是我的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();
    }

}

我需要一个帮助来了解如何对这段代码进行一些转换,以便使其并发。 我已经看到c#也支持Monitors,想知道我是否可以从中受益?

查看您的代码,我建议的第一件事是将您的数据库调用尽可能减少,预先加载当前的拍卖,登录的用户以及相当静态的所有其他内容,然后使用Redis(在内存缓存工具中)或字典中。 (用于二进制搜索性能)

其次,我建议使用ConcurrentDictionary进行实时拍卖跟踪的多线程进程,它具有所需的速度,如果需要线程等待更改,可以将其包装在BlockingCollection上。 这样,您不必使用锁。 此外,您还可以使用队列减轻将数据持久存储到存储中的负担。

如果您有Pluralsight,我建议您参加以下课程:

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