我正在重构一个应用程序,并尝试添加现有函数的异步版本,以提高ASP.NET MVC应用程序的性能时间.我知道异步函数有一个开销,但我预计,如果有足够的迭代次数,从数据库加载数据的I / O密集型性质将不仅可以弥补开销损失,而且还可以获得显着的性能提升.
TermusRepository.LoadByTermusId函数通过从数据库中检索一堆数据表(使用ADO.NET和Oracle Managed Client)来加载数据,填充模型并返回它. TermusRepository.LoadByTermusIdAsync类似,除非它异步执行,当有多个数据表需要检索时,加载数据表下载任务的方法略有不同.
public async Task<ActionResult> AsyncPerformanceTest() { var vm = new AsyncPerformanceTestviewmodel(); Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 60; i++) { TermusRepository.LoadByTermusId<Termus2011_2012EndYear>("1"); TermusRepository.LoadByTermusId<Termus2011_2012EndYear>("5"); TermusRepository.LoadByTermusId<Termus2011_2012EndYear>("6"); TermusRepository.LoadByTermusId<Termus2011_2012EndYear>("7"); } watch.Stop(); vm.NonAsyncElapsedTime = watch.Elapsed; watch.Reset(); watch.Start(); var tasks = new List<Task<Termus2011_2012EndYear>>(); for (int i = 0; i < 60; i++) { tasks.Add(TermusRepository.LoadByTermusIdAsync<Termus2011_2012EndYear>("1")); tasks.Add(TermusRepository.LoadByTermusIdAsync<Termus2011_2012EndYear>("5")); tasks.Add(TermusRepository.LoadByTermusIdAsync<Termus2011_2012EndYear>("6")); tasks.Add(TermusRepository.LoadByTermusIdAsync<Termus2011_2012EndYear>("7")); } await Task.WhenAll(tasks.ToArray()); watch.Stop(); vm.AsyncElapsedTime = watch.Elapsed; return View(vm); } public static async Task<T> LoadByTermusIdAsync<T>(string termusId) where T : Appraisal { var AppraisalHeader = new OracleCommand("select tu.termus_id,tu.manager_username,tu.evaluee_name,tu.evaluee_username,tu.termus_complete_date,termus_start_date,tu.termus_status,tu.termus_version,tn.managername from tercons.termus_users tu left outer join tercons.termus_names tn on tu.termus_id=tn.termus_id where tu.termus_id=:termusid"); AppraisalHeader.BindByName = true; AppraisalHeader.Parameters.Add("termusid",termusId); var dt = await Database.GetDataTableAsync(AppraisalHeader); T Termus = Activator.CreateInstance<T>(); var row = dt.AsEnumerable().Single(); Termus.TermusId = row.Field<decimal>("termus_id").ToString(); Termus.ManagerUsername = row.Field<string>("manager_username"); Termus.EvalueeUsername = row.Field<string>("evaluee_username"); Termus.EvalueeName = row.Field<string>("evaluee_name"); Termus.ManagerName = row.Field<string>("managername"); Termus.TERMUSCompleteDate = row.Field<DateTime?>("termus_complete_date"); Termus.TERMUSStartDate = row.Field<DateTime>("termus_start_date"); Termus.Status = row.Field<string>("termus_status"); Termus.TERMUSVersion = row.Field<string>("termus_version"); Termus.QuestionsAndAnswers = new Dictionary<string,string>(); var RetrieveQuestionIdsCommand = new OracleCommand("select termus_question_id from tercons.termus_questions where termus_version=:termus_version"); RetrieveQuestionIdsCommand.BindByName = true; RetrieveQuestionIdsCommand.Parameters.Add("termus_version",Termus.TERMUSVersion); var QuestionIdsDt = await Database.GetDataTableAsync(RetrieveQuestionIdsCommand); var QuestionIds = QuestionIdsDt.AsEnumerable().Select(r => r.Field<string>("termus_question_id")); //There's about 60 questions/answers,so this should result in 60 calls to the database. It'd be a good spot to combine to a single DB call,but left it this way so I could see if async would speed it up for learning purposes. var DownloadAnswersTasks = new List<Task<DataTable>>(); foreach (var QuestionId in QuestionIds) { var RetrieveAnswerCommand = new OracleCommand("select termus_response,termus_question_id from tercons.termus_responses where termus_id=:termus_id and termus_question_id=:questionid"); RetrieveAnswerCommand.BindByName = true; RetrieveAnswerCommand.Parameters.Add("termus_id",termusId); RetrieveAnswerCommand.Parameters.Add("questionid",QuestionId); DownloadAnswersTasks.Add(Database.GetDataTableAsync(RetrieveAnswerCommand)); } while (DownloadAnswersTasks.Count > 0) { var FinishedDownloadAnswerTask = await Task.WhenAny(DownloadAnswersTasks); DownloadAnswersTasks.Remove(FinishedDownloadAnswerTask); var AnswerDt = await FinishedDownloadAnswerTask; var Answer = AnswerDt.AsEnumerable().Select(r => r.Field<string>("termus_response")).SingleOrDefault(); var QuestionId = AnswerDt.AsEnumerable().Select(r => r.Field<string>("termus_question_id")).SingleOrDefault(); if (!String.IsNullOrEmpty(Answer)) { Termus.QuestionsAndAnswers.Add(QuestionId,System.Net.WebUtility.HtmlDecode(Answer)); } } return Termus; } public static async Task<DataTable> GetDataTableAsync(OracleCommand command) { DataTable dt = new DataTable(); using (var connection = GetDefaultOracleConnection()) { command.Connection = connection; await connection.OpenAsync(); dt.Load(await command.ExecuteReaderAsync()); } return dt; } public static T LoadByTermusId<T>(string TermusId) where T : Appraisal { var RetrieveAppraisalHeaderCommand = new OracleCommand("select tu.termus_id,tn.managername from tercons.termus_users tu left outer join tercons.termus_names tn on tu.termus_id=tn.termus_id where tu.termus_id=:termusid"); RetrieveAppraisalHeaderCommand.BindByName = true; RetrieveAppraisalHeaderCommand.Parameters.Add("termusid",TermusId); var AppraisalHeaderDt = Database.GetDataTable(RetrieveAppraisalHeaderCommand); T Termus = Activator.CreateInstance<T>(); var AppraisalHeaderRow = AppraisalHeaderDt.AsEnumerable().Single(); Termus.TermusId = AppraisalHeaderRow.Field<decimal>("termus_id").ToString(); Termus.ManagerUsername = AppraisalHeaderRow.Field<string>("manager_username"); Termus.EvalueeUsername = AppraisalHeaderRow.Field<string>("evaluee_username"); Termus.EvalueeName = AppraisalHeaderRow.Field<string>("evaluee_name"); Termus.ManagerName = AppraisalHeaderRow.Field<string>("managername"); Termus.TERMUSCompleteDate = AppraisalHeaderRow.Field<DateTime?>("termus_complete_date"); Termus.TERMUSStartDate = AppraisalHeaderRow.Field<DateTime>("termus_start_date"); Termus.Status = AppraisalHeaderRow.Field<string>("termus_status"); Termus.TERMUSVersion = AppraisalHeaderRow.Field<string>("termus_version"); Termus.QuestionsAndAnswers = new Dictionary<string,Termus.TERMUSVersion); var QuestionIdsDt = Database.GetDataTable(RetrieveQuestionIdsCommand); var QuestionIds = QuestionIdsDt.AsEnumerable().Select(r => r.Field<string>("termus_question_id")); //There's about 60 questions/answers,but left it this way so I could see if async would speed it up for learning purposes. foreach (var QuestionId in QuestionIds) { var RetrieveAnswersCommand = new OracleCommand("select termus_response from tercons.termus_responses where termus_id=:termus_id and termus_question_id=:questionid"); RetrieveAnswersCommand.BindByName = true; RetrieveAnswersCommand.Parameters.Add("termus_id",TermusId); RetrieveAnswersCommand.Parameters.Add("questionid",QuestionId); var AnswersDt = Database.GetDataTable(RetrieveAnswersCommand); var Answer = AnswersDt.AsEnumerable().Select(r => r.Field<string>("termus_response")).SingleOrDefault(); if (!String.IsNullOrEmpty(Answer)) { Termus.QuestionsAndAnswers.Add(QuestionId,System.Net.WebUtility.HtmlDecode(Answer)); } } return Termus; } public static DataTable GetDataTable(OracleCommand command) { DataTable dt = new DataTable(); using (var connection = GetDefaultOracleConnection()) { command.Connection = connection; connection.Open(); dt.Load(command.ExecuteReader()); } return dt; } public static OracleConnection GetDefaultOracleConnection() { return new OracleConnection(ConfigurationManager.ConnectionStrings[connectionstringname].ConnectionString); }
60次迭代的结果是:
Non Async 18.4375460 seconds Async 19.8092854 seconds
该测试的结果是一致的.无论我在AsyncPerformanceTest()操作方法中经历了多少次迭代,异步内容比非异步运行慢约1秒. (我连续多次运行测试以考虑JITter预热.)我做错了什么导致异步比非异步慢?我是否误解了编写异步代码的基本原理?