Reputation: 638
I have three classes:
BargeGrouper
class which implements IBargeGrouper
and has Group(IEnumerable<Barge> ungrouped)
method.
BargeGroupMap
class which implements IBargeGroupShow
and has Show(IEnumerable<BargeGroup>
method.
And the class where I call both if these into: GroupingModule
and it's Run()
.
Problem is that when I call Group(ungrouped);
and then want to Show(bargeGroups);
I got IndexOutOfRange
exception in Show(bargeGroups);
because theIEnumerable
I pass to it as a parameter has Count
property many more than actual elements in it. I found out that grouped collection is being returned as empty from Group
most of the time despite I use ContinueWith
to fill that collection with BargeGroup
elements.
Group
method of BargeGrouper
public IEnumerable<BargeGroup> Group(IEnumerable<Barge> ungroupedBarges)
{
List<BargeGroup> bargeGroups = new List<BargeGroup>();
Int32 groupNumber = 0;
var riverBarges = from barge in ungroupedBarges
where barge != null && !String.IsNullOrEmpty(String.Intern(barge.River))
let river = String.Intern(barge.River)
orderby river, barge.MileMarker ascending
group barge by river into barges
select barges.AsEnumerable();
foreach (IEnumerable<Barge> barges in riverBarges)
{
Task.Run(() => ResolveRiver(barges, ref groupNumber)).ContinueWith(t=>
{
IEnumerable<BargeGroup> riverGroups = t.Result;
bargeGroups.AddRange(riverGroups);
});
}
return bargeGroups;
}
ShowBargeGroups
:
public void ShowBargeGroups(IEnumerable<BargeGroup> bargeGroups)
{
Console.WriteLine("Barge groups :");
if (bargeGroups != null)
{
foreach (var group in bargeGroups.Where(b => b != null))
{
var title = String.Format("{0}\n\t | {1} \t\t | {2} \t | {3} \t|", group.Id, "Id", "River", "MileMarker");
Console.WriteLine(title);
foreach (var barge in group.Barges)
{
var caption = String.Format("\t | {0}\t | {1} \t | {2} \t|", barge.Id, barge.River, barge.MileMarker);
Console.WriteLine(caption);
}
}
}
}
And usage in GroupingModule
:.
var groupBarges = bargeGrouper.Group(barges);
bargeGroupShow.ShowBargeGroups(groupBarges);
What can I do to fix this?
Upvotes: 1
Views: 123
Reputation: 149558
When using Task.Run
, which returns a Task
without asynchronously waiting on it, execution of the next line will happen immediately, it won't wait for the delegate passed to complete.
One way of resolving this would be to issue the calls to ResolveRiver
in parallel and asynchronously wait for multiple tasks to complete:
public Task<IEnumerable<BargeGroup>> GroupAsync(IEnumerable<Barge> ungroupedBarges)
{
// Do the grouping
var riverTasks = riverBarges.Select(barges =>
Task.Run(ResolveRiver(barges, ref groupNumber)));
var result = await Task.WhenAll(riverTasks);
bargeGroups.AddRange(result.Result.SelectMany(x => x));
return bargeGroups;
}
And consume it like this:
public async Task FooAsync()
{
var barges = await GroupAsync(ungroupedBarges);
ShowBargeGroups(barges);
}
Note I'd be careful with the ref
parameter being passed and called in parallel, that won't be safe. If you can remove the groupNumber
from the equation, do so.
Upvotes: 2
Reputation: 509
Task.Run in your code creates another awaitable which is most likely going to be executed after your function returns. The easiest solution would be to wait for it with the Wait function.
Task.Run(() => ResolveRiver(barges, ref groupNumber)).ContinueWith(t=>
{
IEnumerable<BargeGroup> riverGroups = t.Result;
bargeGroups.AddRange(riverGroups);
}).Wait()
or event better, you do not need the Task.Run, just waiting for the continuation function would be enough:
ResolveRiver(barges, ref groupNumber)).ContinueWith(t=>
{
IEnumerable<BargeGroup> riverGroups = t.Result;
bargeGroups.AddRange(riverGroups);
}).Wait();
Upvotes: 0
Reputation: 4318
Task.Run doesn't do it's job immediately. So at least you've got a race condition in your code. Try it like this:
public async Task<IEnumerable<BargeGroup>> Group(IEnumerable<Barge> ungroupedBarges)
{
List<BargeGroup> bargeGroups = new List<BargeGroup>();
Int32 groupNumber = 0;
var riverBarges = from barge in ungroupedBarges
where barge != null && !String.IsNullOrEmpty(String.Intern(barge.River))
let river = String.Intern(barge.River)
orderby river, barge.MileMarker ascending
group barge by river into barges
select barges.AsEnumerable();
foreach (IEnumerable<Barge> barges in riverBarges)
{
var riverGroups = await Task.Run(() => ResolveRiver(barges, ref groupNumber));
bargeGroups.AddRange(riverGroups);
}
return bargeGroups;
}
Upvotes: 1
Reputation: 5779
There is no guarantee that by the time you return from the Group
method the Task
is done processing the bargeGroups
list. I suspect that your ResolveRiver
method might take some time.
You need to wait for the task doing the processing to end before returning the enumerable from Group
. I'd suggest making Group
async and awaiting for the Task
:
public async Task<IEnumerable<BargeGroup>> Group(IEnumerable<Barge> ungroupedBarges)
{
List<BargeGroup> bargeGroups = new List<BargeGroup>();
Int32 groupNumber = 0;
var riverBarges = from barge in ungroupedBarges
where barge != null && !String.IsNullOrEmpty(String.Intern(barge.River))
let river = String.Intern(barge.River)
orderby river, barge.MileMarker ascending
group barge by river into barges
select barges.AsEnumerable();
foreach (IEnumerable<Barge> barges in riverBarges)
{
await Task.Run(() => ResolveRiver(barges, ref groupNumber)).ContinueWith(t=>
{
IEnumerable<BargeGroup> riverGroups = t.Result;
bargeGroups.AddRange(riverGroups);
});
}
return bargeGroups;
}
Indeed, the proper way to go would be to make ResolveRiver
async and await it. Then you could get rid of the bargeGroups
List
that you are using to communicate with the Task
.
If you don't want to go the async/await way you may just .Wait()
the Task
.
Upvotes: 0