C# 同步对集合的访问
我有两段代码可以同时在两个不同的线程上运行:C# 同步对集合的访问,c#,.net,multithreading,synchronization,C#,.net,Multithreading,Synchronization,我有两段代码可以同时在两个不同的线程上运行: users = (from user in users orderby user.IsLoggedIn descending , user.Username select user).ToList(); 以及: 第二段代码将在应用程序的主UI线程上运行。如何防止在LINQ操作完成之前将用户设置为null?在getter和setter上使用锁将用户集合封装在属性中是不够的 编辑:
users = (from user in users
orderby user.IsLoggedIn descending ,
user.Username
select user).ToList();
以及:
第二段代码将在应用程序的主UI线程上运行。如何防止在LINQ操作完成之前将用户设置为null?在getter和setter上使用锁将用户集合封装在属性中是不够的
编辑:
我构建了以下测试类:
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; }
}
}
}
没有例外
解决方案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;
}
没有空引用异常,但最终用户为空的断言可能会失败
解决方案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);
}
}
没有例外。我一直有一种唠叨的感觉,那就是那种可能已经存在,但它不一定是原子的
解决方案4:
我无法编译它。volatireRead方法有一个重载获取一个对象,但我无法让它接受一个列表在执行linq查询和设置为null之前,您需要锁定:
lock(syncRoot)
{
users = null;
}
及
其中syncRoot:
private static syncRoot = new object();
LINQ查询将完成users变量是否重置为null,因为ToList强制求值。但是,我不确定这是否能解决您的问题:如果我理解正确,您希望确保不会因为设置为null的时间太早而将过时的集合留在用户中
在这种情况下,您可能只需要声明一个users lock对象,并在setter中以及在query and set语句周围对其进行同步就可以了。将用户保存到局部变量和操作符中,这样您就不必同步任何内容
var tempUsers = users;
if (tempUsers != null)
{
tempUsers = (from user in tempUsers
orderby user.IsLoggedIn descending, user.Username
select user).ToList()
}
锁定什么并不重要,只要两个线程同意锁定同一个对象 我个人会使用局部变量和无锁操作:
var localUsers = Thread.VolatileRead(ref users);
do
{
if (null == localUsers)
{
break;
}
var newLocalUsers = (from user in localUsers
orderby user.IsLoggedIn descending ,
user.Username
select user).ToList();
var currentLocalUsers = Interlocked.CompareExchange(ref users, newLocalUsers, localUsers);
if (currentLocalUsers == localUsers)
{
break;
}
// bummer, try again
localUsers = currentLocalUsers;
} while(true);
另一条线:
Interlocked.Exchange(ref users, null);
我从未尝试过使用Interlocked with var类型,不确定编译器如何接受它,或者它是否必须向下转换为object。最好的方法是调用List.Sort,它将列表排序到位,并且不需要赋值 例如:
users.Sort((a, b) => Math.Sign(
-2 * a.IsLoggedIn.CompareTo(b.IsLoggedIn) + a.UserName.CompareTo(b.UserName)
));
本例假设所有用户都不为null
如果在执行时,另一个线程执行users=null,则旧列表将被排序,但users变量将不受影响。Bleh!福!除非用户是静态的,否则syncRoot不应该是静态的。如果用户是某个方法的本地用户,那么就没有必要锁定它。@darin:关键是用户也被另一个线程分配给了。阅读问题。var不是一种类型。var只是指编译器,此变量的类型是其初始值设定项的类型。@Eric:我更想知道Interlocked.CompareExchange泛型是否能够将linq结果中列表的类型与用户的类型和volatireRead返回类型匹配。后者是对象,我想需要一些演员。啊,我理解。在这种情况下,联锁方法是您最不担心的。如果用户和本地用户都是对象类型,那么编译器应该如何理解查询理解的含义呢?在您开始担心查询结果的类型之前,您必须了解查询的含义!我必须通过将tempUsers分配给用户来完成,对吗?如果是这样,我必须首先检查null,因为我不想在users集合设置为null后填充它;用户不能在另一个线程检查后立即设置为null吗?
var localUsers = Thread.VolatileRead(ref users);
do
{
if (null == localUsers)
{
break;
}
var newLocalUsers = (from user in localUsers
orderby user.IsLoggedIn descending ,
user.Username
select user).ToList();
var currentLocalUsers = Interlocked.CompareExchange(ref users, newLocalUsers, localUsers);
if (currentLocalUsers == localUsers)
{
break;
}
// bummer, try again
localUsers = currentLocalUsers;
} while(true);
Interlocked.Exchange(ref users, null);
users.Sort((a, b) => Math.Sign(
-2 * a.IsLoggedIn.CompareTo(b.IsLoggedIn) + a.UserName.CompareTo(b.UserName)
));