i'm making auction web application using c# asp .net.
my web application got 2 methods must synchronize, can't let them invoke @ same time, because make conflicts in database, i've used lock(object) code blocks protect these 2 methods.
i need make more concurrent, because solution locking full methods makes application slow @ moments.
i've used signalr library send notifications between server , clients, , myregistry class make timer ticks every second , send notifications online clients.
in opinion lines of code access database, , signalr methods sending notifications lines maybe slows system.
here's object that's used lock object:
public static class mutex { public static string lockobject = "mutex"; }
my hub (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 previous client var previousbidder = o in db.userauctioninvested 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 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); } // client 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 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 previous bidder needs 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 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 here's registry class acts timer on web application, ticks every 1 seconds , updates database:
public class myregistry : registry { public readonly log4net.ilog logger = log4net.logmanager.getlogger(system.reflection.methodbase.getcurrentmethod().declaringtype); public myregistry() { schedule(() => { // check if auction on each 1 sec lock (mutex.lockobject) { iep_model db = new iep_model(); var hubcontext = globalhost.connectionmanager.gethubcontext<myhub>(); var auctions = o in db.auctions o.state == "open" select o; var auctionslist = auctions.tolist(); foreach (var auction in auctionslist) { datetime = datetime.now; datetime end = (datetime)auction.close_date_time; // time up! auction "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 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 - 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 idea how transform bit code, make more concurrent. i've seen c# supports monitors, wondering if benefit using it?
looking @ code, first thing suggest reducing db calls few possible, front load current auctions, logged in users, , else static, , either cache them using redis (in memory caching tool) or in dictionaries. (for binary search performance)
secondly suggest multi-threaded process uses concurrentdictionary live auction tracking, has speed looking for, , can wrap on blockingcollection if need thread wait changes. way not have use locks. can off load persisting data storage using queue.
if have pluralsight suggest course:
https://app.pluralsight.com/library/courses/csharp-concurrent-collections/table-of-contents
Comments
Post a Comment