Reputation: 25517
Playing around with yield and Task.
The following simple example runs fine.
class Program
{
private static void Main(string[] args)
{
string[] messages = { "First task", "Second task", "Third task", "Fourth task" };
var taskList = CreateTaskList(messages).ToList();
taskList.ForEach(task => task.Start());
Task.WaitAll(taskList.ToArray());
Console.WriteLine("Main method complete. Press enter to finish.");
Console.ReadLine();
}
static IEnumerable<Task> CreateTaskList(string[] messages)
{
foreach (var message in messages)
{
yield return new Task(obj => PrintMessage((string)obj!), message);
}
}
static void PrintMessage(object message)
{
Console.WriteLine("Message: {0}", message);
}
}
But the following does not. Is some deadlock at play? Its stuck at Task.WaitAll. So all that I get from Console is Before wait all
class SimpleClass
{
public int Counter { get; set; }
}
class Program
{
private static void Main(string[] args)
{
// create the simple object
var simpleObject = new SimpleClass();
var taskList = CreateTaskEnumerable(simpleObject, 10);
// Start all of the tasks
foreach (var task in taskList)
task.Start();
Console.WriteLine("Before wait all");
// wait for all of the tasks to complete
Task.WaitAll(taskList.ToArray());
Console.WriteLine("After wait all");
foreach (var task in taskList)
simpleObject.Counter += task.Result;
// write out the counter value
Console.WriteLine("Expected value {0}, Counter value: {1}", 10000, simpleObject.Counter);
// wait for input before exiting
Console.WriteLine("Press enter to finish");
Console.ReadLine();
}
private static IEnumerable<Task<int>> CreateTaskEnumerable(SimpleClass simpleObject, int numberOfTasks)
{
for (int i = 0; i < numberOfTasks; i++)
{
yield return new Task<int>((stateObject) =>
{
// get the state object
var localCounter = (int)stateObject!;
// enter a loop for 1000 increments
for (int j = 0; j < 1000; j++)
// increment the counters
localCounter++;
return localCounter;
}, simpleObject.Counter);
}
}
}
If I remove yield altogether, the above will be as follows, and works. It gives the output as follows. I am expecting the same output from the above as well, but thats stuck. Why?
Before wait all
After wait all
Expected value 10000, Counter value: 10000
Press enter to finish
The program.
class SimpleClass
{
public int Counter { get; set; }
}
class Program
{
private static void Main(string[] args)
{
// create the bank account instance
var simpleObject = new SimpleClass();
// create an list of tasks
var taskList = new List<Task<int>>();
for (int i = 0; i < 10; i++)
{
// create a new task
var task = new Task<int>((stateObject) =>
{
// get the state object
var localCounter = (int)stateObject!;
// enter a loop for 1000 increments
for (int j = 0; j < 1000; j++)
// increment the counters
localCounter++;
return localCounter;
}, simpleObject.Counter);
taskList.Add(task);
}
// Start all of the tasks
foreach (var task in taskList)
task.Start();
Console.WriteLine("Before wait all");
// wait for all of the tasks to complete
Task.WaitAll(taskList.ToArray());
Console.WriteLine("After wait all");
foreach (var task in taskList)
simpleObject.Counter += task.Result;
// write out the counter value
Console.WriteLine("Expected value {0}, Counter value: {1}", 10000, simpleObject.Counter);
// wait for input before exiting
Console.WriteLine("Press enter to finish");
Console.ReadLine();
}
}
Upvotes: 0
Views: 23
Reputation: 414
In the first example you are calling CreateTaskList(messages).ToList()
which forces CreateTaskList
to yield all tasks before continuing. In the second example you do not call ToList()
, and the tasks are yielded in the foreach
and then started. The problem is in line Task.WaitAll(taskList.ToArray());
. It takes the IEnumerable
and yields the tasks again, and you are waiting for them to finish, but they are not started. In other words, every time you call foreach
or ToList()
on your 'yielded' IEnumerable
, it will run the method CreateTaskEnumerable
and create new tasks.
One solution is to call var taskList = CreateTaskEnumerable(simpleObject, 10).ToList()
or you could just manualy create list in CreateTaskEnumerable
and return it.
P.S. I would suggest you read how yield return
works, or test it in https://sharplab.io/. It basically creates IEnumerable
that gets its data from your method. This means your method will be executed every time your IEnumerable
is enumerated.
Upvotes: 1