Stopwatch具有一个异步操作,等待来自数据库的调用



如何将Stopwatch类与等待在DotNet Core上的异步Post方法中异步调用数据库的异步操作事件一起正确使用?

为什么

为我的代码计时并检查瓶颈。这是一种简化的测试方法,随着时间的推移,它将包含更多的代码

错误

我尝试使用Action事件、Task事件和Func<Task>事件,但没有成功,它们都会给我错误,当我使用EF Core 异步等待来自数据库的调用时,这些错误总是会发生

当我使用动作事件时

System.Private.CoreLib.dll中发生类型为"System.ObjectDisposedException"的未处理异常。无法访问已处理的上下文实例。

当我使用Func<Task>

System.Threading.Tasks.TaskCanceledException:任务已取消。

当我使用Task事件时,它不会打印任何东西 ,并且代码的其余部分执行时没有错误

代码

后方法

public async Task<JsonResult> OnPostTest() {
// my Database Context
using DatabaseContext dc = _dbContext;

// list of json data that will be returned back to the client
List<object>? listJsonData = null;
// stopwatch initialization
Stopwatch sw = new();
sw.LogActionAsync(nameof(OnPostTest), (async () => { // new Task(async () => { // new Func<Task>(async() => {
// get list of data from TableTest database with a valid name and are not marked as delete
List<TableTest> listValidTabletest = await dc.ListTest.AsNoTracking().Where(t => !string.IsNullOrWhiteSpaces(t.strName) && !t.blnDelete).ToListAsync(); //<-- returns a list asynchronously and where the error occurs

// initialize list that will be returned
listJsonData = new();
foreach (TableTest t in listValidTableTest) {
// object that will be in the list of returned json objects
var returnData = new {
t.strName,
t.arrPrices,
t.strStartDate,
t.strEndDate
};
listJsonData.Add(returnData);
}
}));
return new JsonResult(new { 
// return list or an empty array if list has not been initialized
arrJsonData = listJsonData?.toArray() ?? Array.Empty<object>(),
blnGetStatus = bool.TrueString 
});
}

秒表扩展等级

public static async void LogActionAsync(this Stopwatch sw, string? strMethodName, Action asyncAction, int intNbOfIterations = 1) {
sw.Reset();
sw.Start();
List<Task> listOfTasks = new();
for (int i = 0; i < intNbOfIterations; i++) {
listOfTasks.Add(Task.Factory.StartNew(asyncAction)); // for Action event

//listOfTask.Add(asyncTask); // for Task event
}
await Task.WhenAll(listOfTasks);
//await asyncFuncTask; // for Func<Task> event
sw.Stop();
// log duration to a file using Serilog
Log.Debug($"{strMethodName} Action Duration: '{sw.Elapsed.Duration()}'");
}

编辑:我将stopwatch扩展方法更改为async Task LogActionAsync...,将stopwatch对象更改为await sw.LogActionAsync...,但现在没有记录任何内容*。知道吗?

这段代码中有很多错误。总结:

  1. CCD_ 10在两个地方
  2. 缺少awaits
  3. 同时使用单个数据库上下文
  4. 同时添加到结果列表中

所以,让我们逐一解决这些问题。


  1. async void两处
  2. 缺少awaits

正如另一个答案所指出的,LogActionAsync不应该是async void,而是async Taskawait

我将秒表扩展方法更改为async Task LogActionAsync。。。我的秒表对象等待sw。LogActionAsync。。。

您还缺少一个async void。这是一个棘手的问题:当分配给Action变量时,lambda变成async void。没有返回值的异步方法的正确委托类型是Func<Task>,而不是Action

代码:

public static async Task LogActionAsync(this Stopwatch sw, string? strMethodName, Func<Task> asyncAction, int intNbOfIterations = 1) {
sw.Reset();
sw.Start();
List<Task> listOfTasks = new();
for (int i = 0; i < intNbOfIterations; i++) {
listOfTasks.Add(asyncAction());
}
await Task.WhenAll(listOfTasks);
sw.Stop();
// log duration to a file using Serilog
Log.Debug($"{strMethodName} Action Duration: '{sw.Elapsed.Duration()}'");
}

现在,您可以在任何地方正确使用await


  1. 同时使用单个数据库上下文
  2. 同时添加到结果列表中

正如另一个答案所指出的,每个操作lambda需要一个数据库上下文。这是实体框架的限制(反过来又是由大多数SQL在线协议的限制强加的(。

List<T>.Add方法也不是线程安全的,代码可能会同时从多个线程调用它。可以使用并发集合,但返回结果数据更容易、更干净,而不是将修改共享集合作为副作用。

但是,实际上,我怀疑发布的代码中的并发性是一个意外。运行N〃似乎很奇怪;迭代";在进行计时时同时的某些东西;我相信所需的语义是连续运行的N次迭代

如果我的假设是正确的,那么代码应该是这样的:

public static async Task LogActionAsync(this Stopwatch sw, string? strMethodName, Func<Task> asyncAction, int intNbOfIterations = 1) {
sw.Reset();
sw.Start();
for (int i = 0; i < intNbOfIterations; i++) {
await asyncAction();
}
sw.Stop();
// log duration to a file using Serilog
Log.Debug($"{strMethodName} Action Duration: '{sw.Elapsed.Duration()}'");
}
public static async Task<T> LogActionAsync<T>(this Stopwatch sw, string? strMethodName, Func<Task<T>> asyncFunc, int intNbOfIterations = 1) {
sw.Reset();
sw.Start();
T result = default;
for (int i = 0; i < intNbOfIterations; i++) {
result = await asyncFunc();
}
sw.Stop();
// log duration to a file using Serilog
Log.Debug($"{strMethodName} Action Duration: '{sw.Elapsed.Duration()}'");
return result;
}
public async Task<JsonResult> OnPostTest() {
// my Database Context
using DatabaseContext dc = _dbContext;
// list of json data that will be returned back to the client
List<object>? listJsonData = null;
// stopwatch initialization
Stopwatch sw = new();
listJsonData = await sw.LogActionAsync(nameof(OnPostTest), (async () => {
// get list of data from TableTest database with a valid name and are not marked as delete
List<TableTest> listValidTabletest = await dc.ListTest.AsNoTracking().Where(t => !string.IsNullOrWhiteSpaces(t.strName) && !t.blnDelete).ToListAsync();

// initialize list that will be returned
var jsonData = new List<object>();
foreach (TableTest t in listValidTableTest) {
// object that will be in the list of returned json objects
var returnData = new {
t.strName,
t.arrPrices,
t.strStartDate,
t.strEndDate
};
jsonData.Add(returnData);
}
return jsonData;
}));
return new JsonResult(new { 
// return list or an empty array if list has not been initialized
arrJsonData = listJsonData?.toArray() ?? Array.Empty<object>(),
blnGetStatus = bool.TrueString 
});
}

您没有等待对LogActionAsync的调用,所以您的调用发生在页面操作结束之后,这就是为什么您会得到所有这些已处理的异常。您的整个页面及其所有DI对象、数据库上下文和所有内容早就被处理掉了。

async void应该被认为是一个调试工具,它可以帮助发现任何缺乏经验的人立即提出的异步问题!

代码中的问题与StopWatch无关,而是与实体框架有关。

实体框架DbContext不是并发安全的。

您需要将DbContext的创建和处置移动到Task中。

此外,由于奇怪的异常处理,您不应该使用Task.Factory.StartNew。在这种情况下,您不应该使用Task.RunTask.Factory.StartNew,因为您不需要用于并发的线程。

最新更新