这是一个证明IEnumerable
扩展方法不是线程安全的示例。在我的机器上,throw new Exception("BOOM");
线路总是在几秒钟内响起。
希望我已经很好地记录了代码以解释如何触发线程问题。
您可以在linqpad中运行此代码以亲自查看。
async Task Main()
{
// The theory is that it will take a longer time to query a lot of items
// so there should be a better chance that we'll trigger the problem.
var listSize = 999999;
// Specifies how many tasks to spin up. This doesn't necessarily mean
// that it'll spin up the same number of threads, as we're using the thread
// pool to manage that stuff.
var taskCount = 9999;
// We need a list of things to query, but the example here is a bit contrived.
// I'm only calling it `ages` to have a somewhat meaningful variable name.
// This is a distinct list of ints, so, ideally, a filter like:
// `ages.Where(p => p == 4` should only return one result.
// As we'll see below, that's not always the case.
var ages = Enumerable
.Range(0, listSize)
.ToList();
// We'll use `rand` to find a random age in the list.
var rand = new Random();
// We need a reference object to prove that `.Where(...)` below isn't thread safe.
// Each thread is going to modify this shared `person` property in parallel.
var person = new Person();
// Start a bunch of tasks that we'll wait on later. This will run as parallel
// as your machine will allow.
var tasks = Enumerable
.Range(0, taskCount)
.Select(p => Task.Run(() =>
{
// Pick a random age from the list.
var age = ages[rand.Next(0, listSize)];
// These next two lines are where the problem exists.
// We've got multiple threads changing `person.Age` and querying on `person.Age`
// at the same time. As one thread is looping through the `ages` collection
// looking for the `person.Age` value that we're setting here, some other
// thread is going to modify `person.Age`. And every so often, that will
// cause the `.Where(...)` clause to find multiple values.
person.Age = age;
var count = ages.Where(a => a == person.Age).Count();
// Throw an exception if the `.Where(...)` filter returned more than one age.
if (count > 1) {
throw new Exception("BOOM");
}
}));
await Task.WhenAll(tasks);
Console.WriteLine("Done");
}
class Person {
public int Age { get; set; }
}