如果我不等待父方法 C#,则会出现巨大的性能差异



我看到使用和不使用调用方的await之间存在巨大的性能差异。我觉得只有区别应该在返回上。即,如果我使用await则调用者方法应该等待被调用方法的响应,然后再返回到下一个语句,否则调用者方法不需要等待响应,它可以继续执行进一步的语句。

就我而言,如果在调用方方法中使用和不使用await,则存在巨大的性能差异。 即,如果我不使用await,那么它会继续执行调用方中的下一条语句而无需等待,但它比在调用方中使用await要快得多。

另外,我是否正确使用异步和等待?

法典

List<UserViewModel> _ListUser = new List<UserViewModel>();
public XmlElement CreateUpdateUser(Stream input)
{
Main(_ListUser, HttpContext.Current); // using await here makes performance slower and without await it's faster but it returns to the next statement immediately thats the problem.
return FormatResponse("S", "Record(s) created successfully.");
}
public async Task Main(List<UserViewModel> _ListUser, HttpContext current)
{
try
{
WriteToLog("Import Users - Start", 0, DateTime.Now);
UserViewModel _objSiteFileUserSettings = await FillupSiteFileSettings(new UserViewModel());
List<Branch> _branchCollection = await db.Branches.ToListAsync();
List<UserType> _usertypeCollection = await db.UserTypes.ToListAsync();
List<UserStatu> _userstatusCollection = await db.UserStatus.ToListAsync();
List<UserDept> _userdeptCollection = await db.UserDepts.ToListAsync();
List<UserLocation> _userlocationCollection = await db.UserLocations.ToListAsync();
HttpContext.Current = current;
//var tasks = new List<Task>();
foreach (var x in _ListUser)
Update1Record(x, _objSiteFileUserSettings, _branchCollection, _usertypeCollection, _userstatusCollection, _userdeptCollection, _userlocationCollection);
WriteToLog("Import Users - End", 0, DateTime.Now);
}
catch (Exception ex)
{
throw new Exception(ex.ToString());
}
}
public string Update1Record(UserViewModel objUser, UserViewModel _objSiteFileUserSettings, List<Branch> _Lbranch, List<UserType> _Lusertype, List<UserStatu> _Luserstatus, List<UserDept> _Luserdept, List<UserLocation> _Luserlocation)
{
objUser.BranchSiteFile = _objSiteFileUserSettings.BranchSiteFile;
objUser.UsrTypeSiteFile = _objSiteFileUserSettings.UsrTypeSiteFile;
objUser.UsrStatSiteFile = _objSiteFileUserSettings.UsrStatSiteFile;
objUser.BranchId = objUser.Branch != null ? CheckBranch(objUser.Branch, _Lbranch) : null;
objUser.UserDeptId = objUser.UserDept != null ? CheckDept(objUser.UserDept, _Luserdept) : null;
objUser.UserLocationId = objUser.UserLocation != null ? CheckLocation(objUser.UserLocation, _Luserlocation) : null;
objUser.UserStatusId = objUser.UserStatus != null ? CheckStatus(objUser.UserStatus, _Luserstatus) : null;
objUser.UserTypeId = objUser.UserType != null ? CheckType(objUser.UserType, _Lusertype) : 0;
objUser._iEmail = _objSiteFileUserSettings._iEmail;
objUser._iSMS = _objSiteFileUserSettings._iSMS;
using (var VibrantDbContext = new VIBRANT())
using (var AuditDb = new VibrantAuditEntities())
using (var VibrantTransaction = VibrantDbContext.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted))
using (var AuditTransaction = AuditDb.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted))
{
try
{
VibrantDbContext.Configuration.AutoDetectChangesEnabled = false;
objUser.RecordTimeStamp = DateTime.Now;
var _ObjUserItem = FillupDateTimeValues(objUser);
ImportToDB(_ObjUserItem, 0, VibrantDbContext, AuditDb);
BuildImportLog(objUser, VibrantDbContext, AuditDb);
VibrantDbContext.SaveChanges();
AuditDb.SaveChanges();
VibrantTransaction.Commit();
AuditTransaction.Commit();
}
catch (Exception ex)
{
VibrantTransaction.Rollback();
AuditTransaction.Rollback();
throw new Exception(ex.ToString());
}
}
return "S";
}
public XmlElement FormatResponse(string Status, string Message)
{
XmlDocument xmlDoc = new XmlDocument();
XmlNode response = xmlDoc.CreateElement("Response");
xmlDoc.AppendChild(response);
XmlNode statusNode = xmlDoc.CreateElement("Status");
statusNode.InnerText = Status;
response.AppendChild(statusNode);
XmlNode MessageNode = xmlDoc.CreateElement("Message");
MessageNode.InnerText = Message;
response.AppendChild(MessageNode);
return xmlDoc.DocumentElement;
}

不,我想说你还没有掌握什么是 async/await,以及何时使用它们。您应该阅读 await/async 和 Task 以充分理解实现,最重要的是使用此结构的原因。关于异步代码的常见误解是它使代码更快。这通常不准确。它实际上使特定代码稍慢,但它使代码响应更快,并允许您轻松利用服务器的处理能力。

从最简单的意义上说,您编写或调用的代码通常必须等待某些内容。这可能适用于 I/O,例如磁盘访问、数据库或其他通信。它还可能必须等待计算,代码完成需要大量时间才能完成的工作。如果您有几个这样的任务,平均每个任务需要 5 秒,并同步触发它们,则第一个任务将在 5 秒后完成,第二个任务将在 10 秒后完成,第三个任务将在 15 秒后完成。

例如:

var result1 = DoSomething1(); //5 seconds.
var result2 = DoSomething2(); //5 seconds.
var result3 = DoSomething3(); //5 seconds.
var total = result1 + result2 + result3; // executes after 15 seconds.

现在,如果我使DoSomething()async并返回Task<int>

var result1 = DoSomething1(); 
var result2 = DoSomething2(); 
var result3 = DoSomething3(); 
int total = result1 + result2 + result3; // ERROR! result1,2,3 are Taxk<int> representing they are a handle to executing code.

相反,如果我们不关心结果,而只是做一个Console.WriteLine("Done");你几乎会立即达到总线,因为你所做的只是开始 3 倍的任务。现在,每个任务运行并返回结果的时间略长于 5 秒。为了论证起见,假设 5.1 秒。(它会比那少,更像是 .01)此成本损失是针对上下文切换的。您的代码"看起来"已立即运行,因为我们已经达到"完成",而工作线程仍在执行我们的任务。(我们不关心结果吗?处理任何异常吗?

所以,现在我们有 3 个异步方法可以并行运行,我们可以使用 await 来获得结果:

var result1 = await DoSomething1(); var result2 = await DoSomething2(); var result3 = await DoSomething3(); 整数总计 = 结果 1 + 结果 2 + 结果 3;这现在有效。

但是,您认为这需要多长时间才能执行? 答案将是 15.3 秒。通过使用 Await,每个操作都必须等待另一个操作完成。

"嗯,这到底有什么意义?!"我相信有人问过。好吧,要让它们并行运行,你可以这样写:

var result1 = DoSomething1(); var result2 = DoSomething2(); var result3 = DoSomething3(); int 总计 = 等待结果 1 + 等待结果 2 + 等待结果 3;这也行得通。

现在的执行时间?5.1秒。

"啊哈!所以它更快!是的,总体上,但只是因为它可以,当可以安全地为每个操作利用不同的线程并且 await 之前的任何代码的结果不依赖于以前的异步语句中的任何内容处理线程安全引用时。还有其他注意事项,例如同步上下文、异常处理等。另请记住,像 EF 的 DbContext 这样的对象不是线程安全的,因此调用每个对象引用相同的 DbContext 而不按顺序等待它们的可等待方法(因为它应该并行更快)将导致丑陋的小问题和错误。

这只涵盖了 async/await 的基础知识。您应该从Microsoft和其他来源研究更多关于其使用和限制的阅读,最重要的是何时使用它。它不是灵丹妙药,不应该默认为只要可用,因为在大多数情况下,我看到它被使用,它会导致性能问题,以及对非线程安全代码的引用的并发错误。它适用于操作可能需要一段时间才能运行的情况,您可以在等待结果之前安全地完成一些代码或启动并行的独立操作。 如果您的同步代码存在性能问题,则 99.5% 的时间这些问题将无法通过 async/await 解决。在考虑并行操作之前,最好先查看现有代码的详尽可能解释。async/await 的主要参数是使代码响应更快。

一些材料开始:

https://www.codingame.com/playgrounds/4240/your-ultimate-async-await-tutorial-in-c/introduction https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/

我相信会有其他关于SO问题/答案和其他资源的建议。

最后一点:不要这样做:

catch (Exception ex)
{
throw new Exception(ex.ToString());
}

引发新异常会生成新的调用堆栈。如果您没有执行任何操作来处理异常,只需删除 try/catch 块即可。如果您正在做某事并希望异常冒泡,请使用throw

catch (Exception ex)
{
VibrantTransaction.Rollback();
AuditTransaction.Rollback();
throw;
}

这将保留异常的现有调用堆栈。

最新更新