Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/313.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/apache/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 同步对集合的访问_C#_.net_Multithreading_Synchronization - Fatal编程技术网

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)
));