Calculate execution time in async method - c#

I've a BackgroundService that execute a DoWork method.
In ExecuteAsync method, I've a list of 400 items, I measured elapsed time for all process like this:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var listItems = await _myService.GetAll();
var sw = Stopwatch.StartNew();
List<Task> l = new();
listItems.AsParallel().ForAll(x => l.Add(DoWork(x)));
await Task.WhenAll(l);
sw.Stop();
_logger.LogInformation($"{sw.Elapsed.TotalMilliseconds} ms");
}
It's works fine, globally process take 5-6 seconds.
My DoWork method (I simplified as much as possible):
private async Task DoWork(string item)
{
var sw = Stopwatch.StartNew();
var somethingResult = await otherServiceClass.DoSomething(item);
var stuffResult = await otherServiceClass.DoStuff(item);
sw.Stop();
_logger.LogInformation($"{sw.Elapsed.TotalMilliseconds} ms");
}
I would like measured each call of DoWork method, but in log the elapsed time for each call varies between 2-3 seconds.
How is it possible knowing that the overall processing takes 5-6 seconds?
If I change listItems.AsParallel().ForAll(x => l.Add(DoWork(x))); by foreach(var item in listItems) , each call of DoWork method take 50-60 ms !
Is it AsParallel.ForAll which is problematic?
How I can do this knowing that I want to user listItems.AsParallel().ForAll for process.
UPDATE
Here an example of DoSomething method. DoSomething method is declared in my application layer, and calls repository method to retrieve data from database :
public async Task<item> GetItemsIds()
{
return = await _myRepository.GetIdsItems(some parameters...);
}
Here GetIdsItems method :
public async Task<items> GetIdsItems(some parameters...)
{
return await _context.Collection.Where(condition....).FirstOrDefaultAsync().ConfigureAwait(false);
}
My backgroundservice run as Windows Service.

Related

Stopwatch with an async action which awaits calls from a database

How can I properly use the Stopwatch class with an async action event that awaits calls to my database asynchronously inside an async Post method on DotNet Core ?
Why
To time my code and check for bottleneck. This is a simplified test method that will contain more code as time goes on.
Errors
I tried using an Action event, a Task event and a Func<Task> event without success and they all give me errors which always occurs when I am awaiting a call from my database asynchronously using EF Core
When I use Action event
An unhandled exception of type 'System.ObjectDisposedException' occurred in System.Private.CoreLib.dll. Cannot access a disposed context instance.
When I use Func<Task>
System.Threading.Tasks.TaskCanceledException: A task was canceled.
It doesn't print anything when I use Task event and the rest of the code executes without errors
Code
Post Method
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
});
}
Stopwatch Extension Class
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()}'");
}
EDIT:
I changed my stopwatch extension method to async Task LogActionAsync... and my stopwatch object to await sw.LogActionAsync... but now nothing is being logged*. Any idea ?
There's a lot of bugs in this code. To summarize:
async void in two places.
Missing awaits.
Using a single database context concurrently.
Adding to a list of results concurrently.
So, let's fix these one by one.
async void in two places.
Missing awaits.
As another answer noted, LogActionAsync should not be async void but should be async Task and awaited.
I changed my stopwatch extension method to async Task LogActionAsync... and my stopwatch object to await sw.LogActionAsync...
You're still missing one more async void. It's a tricky one: lambdas, when assigned to Action variables, become async void. The proper delegate type for an asynchronous method without a return value is Func<Task>, not Action.
Code:
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()}'");
}
And now you can properly use await everywhere.
Using a single database context concurrently.
Adding to a list of results concurrently.
As another answer noted, you will need one database context per action lambda. This is a limitation of Entity Framework (in turn imposed by a limitation of most SQL on-the-wire protocols).
The List<T>.Add method is also not threadsafe, and the code is potentially invoking it from multiple threads concurrently. It's possible to use a concurrent collection, but it's easier and cleaner to return result data instead of modifying a shared collection as a side effect.
But, really, I suspect that the concurrency in the posted code is an accident. It seems very odd to run N "iterations" of something concurrently when doing timing; I believe the desired semantics are to run N iterations of something serially.
If my assumption is correct, then the code should look like this:
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
});
}
You're not awaiting your call to LogActionAsync, so your call happens after your page action is over, which is why you get all those disposed exceptions. Your entire page and all its DI objects and database contexts and everything have long been disposed by that point.
async void should be considered a debugging tool, it helps find any async issue inexperienced people make right away!
The problem in your code has nothing to do with StopWatch and everything to do with entity framework.
Entity Framework DbContext is not concurrent safe.
You need to move the creation and disposal of the DbContext inside the Task.
Additionally, you should not be using Task.Factory.StartNew due to weird exception handling. And in this case, you should not use Task.Run nor Task.Factory.StartNew because you do not need a thread for concurrency.

Make methods run asynchronously

Can someone please look at this code and tell me what I am doing wrong. It seems to me that this 3 methods should run in the same same, but they run each after another. Please take look at time writen on console. In my opinion all Console.WriteLine should show ~60ms.
Code sample below:
private async void GetOneCombination(string firstMarket, string secondMarket, string thirdMarket, decimal amountOfFirstCurrency)
{
Stopwatch sw = new Stopwatch();
sw.Start();
Task<GetMarketResponse> result = _accessMethods.GetOrderbook(firstMarket);
Console.WriteLine(sw.ElapsedMilliseconds); // ~60ms
Task<GetMarketResponse> result1 = _accessMethods.GetOrderbook(secondMarket);
Console.WriteLine(sw.ElapsedMilliseconds); // ~130 ms
Task<GetMarketResponse> result2 = _accessMethods.GetOrderbook(thirdMarket);
Console.WriteLine(sw.ElapsedMilliseconds); // ~200 ms
var getMarketResponses = await Task.WhenAll(result, result1, result2);
}
Edit:
To be hosnest I thought that it don`t matter whats inside this methods, i thought that no matter what is done inside it will be done 3 times at the same time
public async Task<GetMarketResponse> GetOrderbook(string market = "USD")
{
var address = AddressBook._orderbook + market;
var response = MethodExecutionTimeMeasurer.Invoke(() =>
_client.ExecuteGetAsyncPublic<GetMarketResponse>(address), out timespan);
_logger.LogInformation(string.Format("OrderBook requested for [{0}], response message: {1}. Time[ms]:{2}",
address,
response.Status,
timespan));
return response;
}
and ExecuteGetAsyncPublic:
public async Task<T> ExecuteGetAsyncPublic<T>(string method)
where T : IBasicResponse
{
var response = await _httpClient.GetAsync(method).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var responseData = JsonConvert.DeserializeObject<T>(json);
return responseData;
}
MethodExecutionTimeMeasurer
public static class MethodExecutionTimeMeasurer
{
public static T Invoke<T>(Func<Task<T>> action, out TimeSpan timeSpan)
{
var timer = Stopwatch.StartNew();
var res = action.Invoke();
res.Wait();
timer.Stop();
timeSpan = timer.Elapsed;
return res.Result;
}
public static void Invoke(Action action, out TimeSpan timeSpan)
{
var timer = Stopwatch.StartNew();
action.Invoke();
timer.Stop();
timeSpan = timer.Elapsed;
}
}
There are two problems here:
The GetOrderbook method has an asynchronous signature, but its implementation is synchronous. You are probably getting a warning for the async method lacking an await operator.
The MethodExecutionTimeMeasurer.Invoke has a parameter Func<Task<T>> action (an asynchronous delegate), but the created Task is waited synchronously with the Wait method. So during the task's execution, the current thread is blocked.
Each of the three _accessMethods.GetOrderbook invocations returns a completed task, then the combined task Task.WhenAll(result, result1, result2) is also completed upon creation, and in short from the current thread's perspective nothing is running asynchronously. This case is very similar with a question that was asked yesterday, check it out.
Calling an async Task method does not immediately start on a new thread. It will run on the thread that it was called on until it encounters an await.
so for example
var task = DoSomething();
public async Task DoSomething()
{
// MAIN THREAD
await Task.Delay(1);
// WORKER THREAD
}
If you do it like this it will probably work
public async Task<GetMarketResponse> GetOrderbook(string market = "USD")
{
await Task.Delay(1);
var address = AddressBook._orderbook + market;
var response = MethodExecutionTimeMeasurer.Invoke(() =>
_client.ExecuteGetAsyncPublic<GetMarketResponse>(address), out timespan);
_logger.LogInformation(string.Format("OrderBook requested for [{0}], response message: {1}. Time[ms]:{2}",
address,
response.Status,
timespan));
return response;
}
Another approach you can take is
Parallel.Invoke(
() => _accessMethods.GetOrderbook(firstMarket).Wait(),
() => _accessMethods.GetOrderbook(secondMarket).Wait(),
() => _accessMethods.GetOrderbook(thirdMarket).Wait(),
);

C# TaskWhenAll on method which is returing result depending on awaited call

i'm trying to make this code works in async way, but i got some doubts.
public async Task<string> GetAsync(string inputA, string inputB)
{
var result = await AnotherGetAsync(inputA, inputB)
.ConfigureAwait(false);
return result.Enabled
? inputB
: string.Empty;
}
I got some string input collection, and i would like to run this method on them.
Then i would do Task.WhenAll, and filter for non empty strings.
But it won't be async as inside the method i'm already awaiting for the result right?
I assumed the real question is:
If a single item is awaited inside method A, will this run sequential if I use Task.WhenAll for a range of items calling method A?
They will be run simultaneous with Task.WhenAll.
Perhaps it is best explained with an example:
void Main()
{
Test().GetAwaiter().GetResult();
}
private async Task<bool> CheckEmpty(string input)
{
await Task.Delay(200);
return String.IsNullOrEmpty(input);
}
private async Task Test()
{
var list = new List<string>
{
"1",
null,
"2",
""
};
var stopwatch = new Stopwatch();
stopwatch.Start();
// This takes aprox 4 * 200ms == 800ms to complete.
foreach (var itm in list)
{
Console.WriteLine(await CheckEmpty(itm));
}
Console.WriteLine(stopwatch.Elapsed);
Console.WriteLine();
stopwatch.Reset();
stopwatch.Start();
// This takes aprox 200ms to complete.
var tasks = list.Select(itm => CheckEmpty(itm));
var results = await Task.WhenAll(tasks); // Runs all at once.
Console.WriteLine(String.Join(Environment.NewLine, results));
Console.WriteLine(stopwatch.Elapsed);
}
My test results:
False
True
False
True
00:00:00.8006182
False
True
False
True
00:00:00.2017568
If we break this down:
AnotherGetAsync(inputA, inputB)
Returns a Task. Using the await keyword implicitly returns another Task to the code calling GetAsync(string inputA, string inputB).
Using the await keyword will free up the current thread, which is what makes the code asynchronous.
once the Task returned by AnotherGetAsync(inputA, inputB) is complete, result will be set to that Task.Result.
The remainder of the method will then resume:
return result.Enabled
? inputB
: string.Empty;
Setting the Task.Result of the Task returned from GetAsync.
If GetAsync is to be run multiple times, you can use Task.WhenAll to wait for each resulting Task to complete:
Task.WhenAll(yourStringCollection.Select(s => GetAsync(s, "another string"));
Task.WhenAll itself returns another Task that will complete when all the GetAsync tasks have completed:
var strings = await Task.WhenAll(yourStringCollection.Select(s => GetAsync(s, "another string"));
var nonEmptyStrings = strings.Where(s => s != string.Empty);
No, it will be async. When you await a task inside an async method, the task will be awaited when the async method is invoked. Here is your example simplified, using explicit variables for the created tasks:
public async Task<string> GetAsync()
{
var innerTask = InnerGetAsync();
var result = await innerTask;
var stringResult = result.ToString();
return stringResult;
}
Later you create two tasks by invoking the GetAsync() method:
var task1 = GetAsync();
var task2 = GetAsync();
At this point the two tasks are running concurrently. Each one has invoked internally the InnerGetAsync() method, which means that the two innerTasks are also up and running, and awaited. Any code that follows the await will not run before the innerTask is completed.
The completion of each outer task (task1 and task2) is dependent on the completion of its innerTask. Any code that will await task1, will also implicitly await innerTask as well.
After creating the two tasks, you combine them with Task.WhenAll, then do something else, then await the combined task, and finally process the results:
Task<string[]> whenAllTask = Task.WhenAll(task1, task2);
DoSomethingElse();
string[] results = await whenAllTask;
ProcessResults(results);
It is important that the DoSomethingElse() method will run before the two tasks are completed. So at this point three things will be happening concurrently, the DoSomethingElse() and the two active tasks. The await inside the GetAsync() method is local to this method, and does not mean that the generated outer task will be automatically awaited upon creation. To process the results of task1 and task2 you must first await them as well.

Sharing variable in ContinueWith(anotherTask) + C# Task Parallel Library

I create a task with continuationWith(anotherTask) as below. I want to find out the time taken for completing its work by the first task. I share the variable "task1StartedDateTime" between task1 and child task. Will this work without any issues?
public static void MyMethod()
{
var task1StartedDateTime = DateTime.Now;
var task1 = doWorkAsync();
task1.ContinueWith(t1 => {
var task1TookTime = DateTime.Now - task1StartedDateTime;
Console.WriteLine($"Task 1 took {task1TookTime}");
//Some other work of this child task
});
}
Yes it will work. However it should be better to make use of the StopWatch class, since this is a more accurate and efficient way of calculating elapsed time of a method, process whatever running on a machine. For more info related to the latter argument, please have a look here:
var stopwatch = StopWatch.StartNew();
var task1 = doWorkAsync();
task1.ContinueWith(t1 => {
stopwatch.Stop();
Console.WriteLine($"Task 1 took {stopwatch.EllapsedMilliseconds} ms.");
//Some other work of this child task
}
Yes, you can use captured variables in a lambda - captured variables closed over in this way will be promoted to an anonymous class instance, to ensure they can outlive the method they are declared in, and to allow sharing between the outer method and the continuation.
However, you should use aStopwatch for measuring time - it is more accurate.
In .Net 4.5 and later, you also have the option to replace the continuation in .ContinueWith to an awaited continuation - this has additional guarantees, and is easier to read:
public static async Task MyMethod()
{
var sw = new Stopwatch();
sw.Start();
await doWorkAsync();
var task1TookTime = sw.Elapsed;
Console.WriteLine($"Task 1 took {task1TookTime}");
//Some other work of this child task
}
(Although note that if MyMethod is awaited, that the Task will only complete once doWorkAsync and the timer logging is complete, which differs from your original implementation).

Using the Stopwatch with Async methods

I have some code as follows:
public void Start()
{
var watch = new Stopwatch();
watch.Start();
Task.Factory.StartNew(MyMethod1);
Task.Factory.StartNew(MyMethod2);
watch.Stop();
Log(watch.ElapsedMilliseconds);
Task.Factory.StartNew(MyMethod3);
}
Because MyMethod1 and MyMethod2 are called Asynchronously watch.Stop() gets called at the wrong time. How I can ensure that .Stop gets called and logged after MyMethod1 and MyMethod2 finish BUT ensure that MyMethod3 does not have to wait.
I want to keep all Stopwatch functionality in my Start() method and not have the logging in any of my 3 methods i.e. MyMethod1, MyMethod2 and MyMethod3
You can use the Task.Factory.ContinueWhenAll method.
watch.Start();
var t1 = Task.Factory.StartNew(MyMethod1);
var t2 = Task.Factory.StartNew(MyMethod2);
Task.Factory.ContinueWhenAll(new [] {t1, t2}, tasks => watch.Stop());
If you're targeting for .NET 4.5 and upper, you can also use the method Task.WhenAll. It returns a task that will complete when all of the passed Task objects have completed.
Task.WhenAll(t1, t2).ContinueWith(t => watch.Stop());
You need create a new Thread that will handle the logging issue.
This logging thread will wait on EventWaitHandle.WaitAll(threadsEventWaitHandles) that will contain all the threads EventWaitHandles.
Something like that :
private void LoggingThread()
{
var watch = new Stopwatch();
watch.Start();
EventWaitHandle.WaitAll(threadsEventWaitHandles);
watch.Stop();
Log(watch.ElapsedMilliseconds);
}
And also the methods MyMethod1, MyMethod2 will signal to the loging thread whene they finish.
Something like that :
private void MyMethod1()
{
//... your code
EventWaitHandle.Set();
}
private void MyMethod2()
{
//... your code
EventWaitHandle.Set();
}
So you can ensure that MyMethod3 does not have to wait.
public void Start()
{
var watch = new Stopwatch();
watch.Start();
Task.Factory.StartNew(MyMethod1);
Task.Factory.StartNew(MyMethod2);
Task.WaitAll(); // Wait for previous tasks to finish
watch.Stop();
Log(watch.ElapsedMilliseconds);
Task.Factory.StartNew(MyMethod3);
}

Categories