I have these two pieces of code that could possibly be run at the same time on two different threads:
users = (from user in users
orderby user.IsLoggedIn descending ,
user.Username
select user).ToList();
and:
users=null;
The second piece of code will run on the main UI thread of the application. How can I prevent users to be set to null before the LINQ operation completes? Encapsulating the user collection in a property with locks on the getter and setter will not be enough methinks...
EDIT: I constructed the following testing classes:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace MultiThreading_Spike
{
class Program
{
private static List<User> users;
private static Timer timer;
static void Main(string[] args)
{
timer=new Timer(OrderUsers,null,5,5);
for (int i = 0; i < 10000; i++)
{
ResetUsers();
Thread.Sleep(5);
users = new List<User>
{
new User {UserName = "John"},
new User {UserName = "Peter"},
new User {UserName = "Vince"},
new User {UserName = "Mike"}
};
Thread.Sleep(5);
}
ResetUsers();
Thread.Sleep(5)
Debug.Assert(users==null);
}
private static void OrderUsers(object state)
{
if(users==null)return;
Thread.Sleep(2);
try
{
users = (from user in users
orderby user.IsLoggedIn descending ,
user.UserName
select user).ToList();
}
catch(Exception e)
{
Console.WriteLine("Error: {0}",e.Message);
}
}
private static void ResetUsers()
{
users = null;
}
}
public class User
{
bool isLoggedIn;
public bool IsLoggedIn
{
get { return isLoggedIn; }
set { isLoggedIn = value; }
}
private string userName;
public string UserName
{
get { return userName; }
set { userName = value; }
}
}
}
This code fails with a null reference exception in the OrderUsers method. Then I implemented the suggested solutions: Solution 1:
//identical code omitted
private static void OrderUsers(object state)
{
lock(syncRoot)
{
if(users==null)return;
Thread.Sleep(2);
try
{
users = (from user in users
orderby user.IsLoggedIn descending ,
user.UserName
select user).ToList();
}
catch(Exception e)
{
Console.WriteLine("Error: {0}",e.Message);
}
}
}
private static void ResetUsers()
{
lock(syncRoot)
{
users = null;
}
}
}
No exceptions!
Solution 2:
private static void OrderUsers(object state)
{
if(users==null)return;
var tempUsers = users;
Thread.Sleep(2);
try
{
tempUsers = (from user in tempUsers
orderby user.IsLoggedIn descending ,
user.UserName
select user).ToList();
}
catch(Exception e)
{
Console.WriteLine("Error: {0}",e.Message);
}
users = tempUsers;
}
No null reference exceptions, but the Assert for users to be null in the end can fail.
Solution 3:
private static void OrderUsers(object state)
{
if(users==null)return;
try
{
users.Sort((a, b) => Math.Sign(-2 * a.IsLoggedIn.CompareTo(b.IsLoggedIn) + a.UserName.CompareTo(b.UserName)));
}
catch(Exception e)
{
Console.WriteLine("Error: {0}",e.Message);
}
}
No exceptions. I keep having the nagging feeling that the Sort may be "in place", but that it is not necessarily atomic.
Solution 4: I could not get it to compile. The VolatileRead method has an overload taking an object, but I could not make it accept a List