I have two services that ultimately both update the same object, so we have a test to ensure that the writes to that object complete (Under the hood we have retry policies on each).
9 times out of 10, one or more of the theories will fail, with the task.ShouldNotBeNull(); always being the assertion to fail. What am i getting wrong with the async code in this sample? Why would the task be null?
[Theory]
[InlineData(1)]
[InlineData(5)]
[InlineData(10)]
[InlineData(20)]
public async Task ConcurrencyIssueTest(int iterations)
{
var orderResult = await _driver.PlaceOrder();
var tasksA = new List<Task<ApiResponse<string>>>();
var tasksB = new List<Task<ApiResponse<string>>>();
await Task.Run(() => Parallel.For(1, iterations,
x =>
{
tasksA.Add(_Api.TaskA(orderResult.OrderId));
tasksB.Add(_Api.TaskB(orderResult.OrderId));
}));
//Check all tasks return successful
foreach (var task in tasksA)
{
task.ShouldNotBeNull();
var result = task.GetAwaiter().GetResult();
result.ShouldNotBeNull();
result.StatusCode.ShouldBe(HttpStatusCode.OK);
}
foreach (var task in tasksB)
{
task.ShouldNotBeNull();
var result = task.GetAwaiter().GetResult();
result.ShouldNotBeNull();
result.StatusCode.ShouldBe(HttpStatusCode.OK);
}
}
}
There's no need for Tasks and Parrallel looping here. I'm presuming that your _api calls are IO bound? You want something more like this:
var tasksA = new List<Task<ApiResponse<string>>>();
var tasksB = new List<Task<ApiResponse<string>>>();
//fire off all the async tasks
foreach(var it in iterations){
tasksA.Add(_Api.TaskA(orderResult.OrderId));
tasksB.Add(_Api.TaskB(orderResult.OrderId));
}
//await the results
await Task.WhenAll(tasksA).ConfigureAwait(false);
foreach (var task in tasksA)
{
//no need to get GetAwaiter(), you've awaited above.
task.Result;
}
//to get the most out of the async only await them just before you need them
await Task.WhenAll(tasksB).ConfigureAwait(false);
foreach (var task2 in tasksB)
{
task2.Result;
}
this will fire all your api calls async then block while the results return. You Parallel for and tasks are just using additional thread pool threads to zero benefit.
If _api is CPU bound you could get benefit from Task.Run but I'm guessing these are web api or something. So the Task.Run is doing nothing but using an additional thread.
As others have suggested, remove the Parallel, and await on all tasks to finish before asserting them.
I would also recommend to remove .Result from each task, and await them instead.
public async Task ConcurrencyIssueTest(int iterations)
{
var orderResult = await _driver.PlaceOrder();
var taskA = _Api.TaskA(orderResult.OrderId);
var taskB = _Api.TaskB(orderResult.OrderId);
await Task.WhenAll(taskA, taskB);
var taskAResult = await taskA;
taskAResult.ShouldNotBeNull();
taskAResult.StatusCode.ShouldBe(HttpStatusCode.OK);
var taskBResult = await taskB;
taskBResult.ShouldNotBeNull();
taskBResult.StatusCode.ShouldBe(HttpStatusCode.OK);
}
Related
I am trying to optimize this code to decrease the time taken to complete the forloop. In this case, CreateNotification() takes a long time and using async await does not improve performance as each asynchronous call is being awaited. I would like to use Task.WhenAll() to optimize the code. How can I do this?
foreach (var notification in notificationsInput.Notifications)
{
try
{
var result = await CreateNotification(notification);
notification.Result = result;
}
catch (Exception exception)
{
notification.Result = null;
}
notifications.Add(notification);
}
You can call Select on the collection whose elements you want to process in parallel, passing an asynchronous delegate to it. This asynchronous delegate would return a Task for each element that's processed, so you could then call Task.WhenAll on all these tasks. The pattern is like so:
var tasks = collection.Select(async (x) => await ProcessAsync(x));
await Task.WhenAll(tasks);
For your example:
var tasks = notificationsInput.Notifications.Select(async (notification) =>
{
try
{
var result = await CreateNotification(notification);
notification.Result = result;
}
catch (Exception exception)
{
notification.Result = null;
}
});
await Task.WhenAll(tasks);
This assumes that CreateNotification is thread-safe.
Update
You will need to install DataFlow to use this solution
https://www.nuget.org/packages/System.Threading.Tasks.Dataflow/
Depending on what CreateNotification is and whether you want to run this in parallel.
You could use a DataFlow ActionBlock, it will give you the best of both worlds if this is IO bound or Mix IO/CPU bound operations and let you run async and in parallel
public static async Task DoWorkLoads(NotificationsInput notificationsInput)
{
var options = new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 50
};
var block = new ActionBlock<Something>(MyMethodAsync, options);
foreach (var notification in notificationsInput.Notifications)
block.Post(notification);
block.Complete();
await block.Completion;
}
...
public async Task MyMethodAsync(Notification notification)
{
var result = await CreateNotification(notification);
notification.Result = result;
}
Add pepper and salt to taste.
I think this ought to be equivalent to your code:
var notifications = new ConcurrentBag<Notification>();
var tasks = new List<Task>();
foreach (var notification in notificationsInput.Notifications)
{
var task = CreateNotification(notification)
.ContinueWith(t =>
{
if (t.Exception != null)
{
notification.Result = null;
}
else
{
notification.Result = t.Result;
}
notifications.Add(notification);
});
tasks.Add(task);
}
await Task.WhenAll(tasks);
.ContinueWith( will receive the completed/failed task from CreateNotification(, and is itself a task. We add the ContinueWith task to a list and use that in the WhenAll(.
I'm using a ConcurrentBag for notifications so that you can add from multiple threads safely. If you want to turn this into a regular list, you can call var regularListNotifications = notifications.ToList(); (assuming you have a using for LINQ).
I'm not sure if I'm misunderstanding the usage of Task.WhenAny but in the following code only "0" gets printed when it should print "1" and "2" and then "mx.Name" every time a task finishes:
public async void PopulateMxRecords(List<string> nsRecords, int threads)
{
ThreadPool.SetMinThreads(threads, threads);
var resolver = new DnsStubResolver();
var tasks = nsRecords.Select(ns => resolver.ResolveAsync<MxRecord>(ns, RecordType.Mx));
Console.WriteLine("0");
var finished = Task.WhenAny(tasks);
Console.WriteLine("1");
while (mxNsRecords.Count < nsRecords.Count)
{
Console.WriteLine("2");
var task = await finished;
var mxRecords = await task;
foreach(var mx in mxRecords)
Console.WriteLine(mx.Name);
}
}
The DnsStubResolver is part of ARSoft.Tools.Net.Dns. The nsRecords list contains up to 2 million strings.
I'm not sure if I'm misunderstanding the usage of Task.WhenAny
You might be. The pattern you seem to be looking for is interleaving. In the following example, notice the important changes that I have made:
use ToList() to materialize the LINQ query results,
move WhenAny() into the loop,
use Remove(task) as each task completes, and
run the while loop as long as tasks.Count() > 0.
Those are the important changes. The other changes are there to make your listing into a runnable demo of interleaving, the full listing of which is here: https://dotnetfiddle.net/nr1gQ7
public static async Task PopulateMxRecords(List<string> nsRecords)
{
var tasks = nsRecords.Select(ns => ResolveAsync(ns)).ToList();
while (tasks.Count() > 0)
{
var task = await Task.WhenAny(tasks);
tasks.Remove(task);
var mxRecords = await task;
Console.WriteLine(mxRecords);
}
}
I'm trying to use Task.WaitAll on a list of tasks. The thing is the tasks are an async lambda which breaks Tasks.WaitAll as it never waits.
Here is an example code block:
List<Task> tasks = new List<Task>();
tasks.Add(Task.Factory.StartNew(async () =>
{
using (dbContext = new DatabaseContext())
{
var records = await dbContext.Where(r => r.Id = 100).ToListAsync();
//do long cpu process here...
}
}
Task.WaitAll(tasks);
//do more stuff here
This doesn't wait because of the async lambda. So how am I supposed to await I/O operations in my lambda?
Task.Factory.StartNew doesn't recognise async delegates as there is no overload that accepts a function returning a Task.
This plus other reasons (see StartNew is dangerous) is why you should be using Task.Run here:
tasks.Add(Task.Run(async () => ...
This doesn't wait because of the async lambda. So how am I supposed to
await I/O operations in my lambda?
The reason Task.WaitAll doesn't wait for the completion of the IO work presented by your async lambda is because Task.Factory.StartNew actually returns a Task<Task>. Since your list is a List<Task> (and Task<T> derives from Task), you wait on the outer task started by StartNew, while ignoring the inner one created by the async lambda. This is why they say Task.Factory.StartNew is dangerous with respect to async.
How could you fix this? You could explicitly call Task<Task>.Unwrap() in order to get the inner task:
List<Task> tasks = new List<Task>();
tasks.Add(Task.Factory.StartNew(async () =>
{
using (dbContext = new DatabaseContext())
{
var records = await dbContext.Where(r => r.Id = 100).ToListAsync();
//do long cpu process here...
}
}).Unwrap());
Or like others said, you could call Task.Run instead:
tasks.Add(Task.Run(async () => /* lambda */);
Also, since you want to be doing things right, you'll want to use Task.WhenAll, why is asynchronously waitable, instead of Task.WaitAll which synchronously blocks:
await Task.WhenAll(tasks);
You can do like this.
void Something()
{
List<Task> tasks = new List<Task>();
tasks.Add(ReadAsync());
Task.WaitAll(tasks.ToArray());
}
async Task ReadAsync() {
using (dbContext = new DatabaseContext())
{
var records = await dbContext.Where(r => r.Id = 100).ToListAsync();
//do long cpu process here...
}
}
you have to use the Task.ContinueWith method. Like this
List<Task> tasks = new List<Task>();
tasks.Add(Task.Factory.StartNew(() =>
{
using (dbContext = new DatabaseContext())
{
return dbContext.Where(r => r.Id = 100).ToListAsync().ContinueWith(t =>
{
var records = t.Result;
// do long cpu process here...
});
}
}
}
Update
Added the missing code for adding in taskList
I have a list of task, that I await on..
var files = Directory.GetFiles(myFilesDirectory);
var listOfTasks = new List<Tasks>();
files.ToList().ForEach(file => {
var localFile = file // to avoid any closure issue
listOfTasks.Add(ProcessMyFileTask(localFile));
});
await Task.WhenAll(listOfTasks.ToArray());
Console.WriteLine("All done!");
Here's the ProcessMyFileTask
private async Task<List<string>> ProcessMyFileTask(string filePath)
{
using (var streamReader = File.OpenText(filePath))
{
string line;
if ((line = await streamReader.ReadLineAsync()) != null)
{
return DumpHexInLog(line);
}
return null;
}
}
The message shows up when all files are processed. But if I add a continuation task, like this..
var files = Directory.GetFiles(myFilesDirectory);
var listOfTasks = new List<Tasks>();
files.ToList().ForEach(file => {
var localFile = file // to avoid any closure issue
listOfTasks.Add(ProcessMyFileTask(localFile).ContinueWith(list =>
ValidateHexDumpsTask(list.Result, localFile)));
});
await Task.WhenAll(listOfTasks.ToArray());
Console.WriteLine("All done!");
Then what Tasks will be awaited on? I mean would "All Done!" come after all the ProcessMyFileTask is done? Or will it come after all the ValidateHexDumpsTask are done too?
When I tested it, it came after the ValidateHexDumpsTask but I am not sure if that will certainly be always the case, as this might have been because of some threading condition or such.
It will complete only when both the ProcessMyFiles methods and ValidateHexDumps are done.
However, ContinueWith is not recommended. It's a low-level, dangerous API. You should use await instead:
var files = Directory.GetFiles(myFilesDirectory);
var listOfTasks = files.Select(ProcessAndValidateAsync);
await Task.WhenAll(listOfTasks);
Console.WriteLine("All done!");
async Task ProcessAndValidateAsync(string file)
{
var list = await ProcessMyFileTask(localFile);
ValidateHexDumps(list, localFile);
}
WhenAll will be completed when ValidateHexDumps has finished running on each of the items.
ContinueWith returns a Task that represents the completion of the continuation, not the completion of the task that it is a continuation of.
I'm trying to process each individual request as it finishes, which is what would happen in the ContinueWith after the GetStringAsync and then when they've all completed have an additional bit of processing.
However, it seems that the ContinueWith on the WhenAll fires right away. It appears as if the GetStringAsync tasks are faulting, but I can't figure out why.
When I use WaitAll instead of WhenAll and just add my processing after the WaitAll then my requests work just fine. But when I change it to WhenAll it fails.
Here is an example of my code:
using (var client = new HttpClient())
{
Task.WhenAll(services.Select(x =>
{
return client.GetStringAsync(x.Url).ContinueWith(response =>
{
Console.WriteLine(response.Result);
}, TaskContinuationOptions.AttachedToParent);
}).ToArray())
.ContinueWith(response =>
{
Console.WriteLine("All tasks completed");
});
}
You shouldn't use ContinueWith and TaskContinuationOptions.AttachedToParent when using async-await. Use async-await only, instead:
async Task<IEnumerable<string>> SomeMethod(...)
{
using (var client = new HttpClient())
{
var ss = await Task.WhenAll(services.Select(async x =>
{
var s = await client.GetStringAsync(x.Url);
Console.WriteLine(response);
return s;
};
Console.WriteLine("All tasks completed");
return ss;
}
}
Well, I found the issue. I'll leave it here in case anyone else comes along looking for a similar answer. I still needed to await the Task.WhenAll method.
So, the correct code would be:
using (var client = new HttpClient())
{
await Task.WhenAll(services.Select(x =>
{
return client.GetStringAsync(x.Url).ContinueWith(response =>
{
Console.WriteLine(response.Result);
}, TaskContinuationOptions.AttachedToParent);
}).ToArray())
.ContinueWith(response =>
{
Console.WriteLine("All tasks completed");
});
}
I still see a couple issues with your solution:
Drop the using statement - you don't want to dispose HttpClient.
Drop the ContinueWiths - you don't need them if your are using await properly.
The Task.WhenAny approach described in this MSDN article is a somewhat cleaner way to process tasks as they complete.
I would re-write your example like this:
var client = new HttpClient();
var tasks = services.Select(x => client.GetStringAsync(x.Url)).ToList();
while (tasks.Count > 0)
{
var firstDone = await Task.WhenAny(tasks);
tasks.Remove(firstDone);
Console.WriteLine(await firstDone);
}
Console.WriteLine("All tasks completed");
Edit to address comment:
If you need access to the service object as the tasks complete, one way would be to modify tasks to be a list of Task<ObjectWithMoreData> instead of Task<string>. Notice the lambda is marked async so we can await within it:
var client = new HttpClient();
var tasks = services.Select(async x => new
{
Service = x,
ResponseText = await client.GetStringAsync(x.Url)
}).ToList();
while (tasks.Count > 0)
{
var firstDone = await Task.WhenAny(tasks);
tasks.Remove(firstDone);
var result = await firstDone;
Console.WriteLine(result.ResponseText);
// do something with result.Service
}
Console.WriteLine("All tasks completed");