【发布时间】:2016-09-01 22:39:31
【问题描述】:
我正在使用 c# asp .net 制作拍卖网络应用程序。
我的 Web 应用程序有 2 个必须同步的方法,不能让它们同时调用,因为它们可能会在我的数据库中产生冲突,所以我只使用了 lock(object) 代码块来保护这 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 应用程序中充当计时器,每隔一秒计时一次并更新数据库:
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,想知道我是否会从使用它中受益?
【问题讨论】:
-
任何形式的对远程资源(如数据库)访问的锁定都会扼杀并发性(除了非常脆弱之外)。您应该寻找的最有可能的解决方案是重新构建数据库以防止损坏,而不是在应用级别处理更新失败。
标签: c# asp.net multithreading concurrency signalr