C# 多线程、lambdas和局部变量

C# 多线程、lambdas和局部变量,c#,multithreading,.net-4.0,lambda,task-parallel-library,C#,Multithreading,.net 4.0,Lambda,Task Parallel Library,我的问题是,在下面的代码中,我是否可以确保实例方法将访问我认为它们将访问的变量,或者在我仍在工作时,它们是否可以由另一个线程更改?闭包是否与此有关,即我是否将处理IEnumerable的本地副本,以便枚举是安全的 套用我的问题,如果我从未写入共享变量,是否需要任何锁 public class CustomerClass { private Config cfg = (Config)ConfigurationManager.GetSection("Customer"); publ

我的问题是,在下面的代码中,我是否可以确保实例方法将访问我认为它们将访问的变量,或者在我仍在工作时,它们是否可以由另一个线程更改?闭包是否与此有关,即我是否将处理
IEnumerable
的本地副本,以便枚举是安全的

套用我的问题,如果我从未写入共享变量,是否需要任何锁

public class CustomerClass
{
    private Config cfg = (Config)ConfigurationManager.GetSection("Customer");

    public void Run()
    {
        var serviceGroups = this.cfg.ServiceDeskGroups.Select(n => n.Group).ToList();

        var groupedData = DataReader.GetSourceData().AsEnumerable().GroupBy(n => n.Field<int>("ID"));
        Parallel.ForEach<IGrouping<int, DataRow>, CustomerDataContext>(
            groupedData,
            () => new CustomerDataContext(),
            (g, _, ctx) =>
            {
                var inter = this.FindOrCreateInteraction(ctx, g.Key);

                inter.ID = g.Key;
                inter.Title = g.First().Field<string>("Title");

                this.CalculateSomeProperty(ref inter, serviceGroups);

                return ctx;
            },
            ctx => ctx.SubmitAllChanges());
    }

    private Interaction FindOrCreateInteraction(CustomerDataContext ctx, int ID)
    {
        var inter = ctx.Interactions.Where(n => n.Id = ID).SingleOrDefault();

        if (inter == null)
        {
            inter = new Interaction();
            ctx.InsertOnSubmit(inter);
        }

        return inter;
    }

    private void CalculateSomeProperty(ref Interaction inter, IEnumerable<string> serviceDeskGroups)
    {
        // Reads from the List<T> class instance variable. Changes the state of the ref'd object.
        if (serviceGroups.Contains(inter.Group))
        {
            inter.Ours = true;
        }
    }
}
公共类CustomerClass
{
私有配置cfg=(Config)ConfigurationManager.GetSection(“客户”);
公开募捐
{
var serviceGroups=this.cfg.ServiceDeskGroups.Select(n=>n.Group.ToList();
var groupedData=DataReader.GetSourceData().AsEnumerable().GroupBy(n=>n.Field(“ID”);
并行ForEach(
分组数据,
()=>新建CustomerDataContext(),
(g,ctx)=>
{
var inter=this.FindOrCreateInteraction(ctx,g.Key);
inter.ID=g.键;
inter.Title=g.First()字段(“Title”);
此.CalculateMeProperty(参考inter,serviceGroups);
返回ctx;
},
ctx=>ctx.SubmitAllChanges());
}
私有交互FindOrCreateInteraction(CustomerDataContext ctx,int-ID)
{
var inter=ctx.Interactions.Where(n=>n.Id=Id).SingleOrDefault();
if(inter==null)
{
inter=新交互();
ctx.InsertOnSubmit(国际);
}
返回间隔;
}
私有void CalculateMeProperty(参考交互,IEnumerable serviceDeskGroups)
{
//从列表类实例变量读取。更改引用对象的状态。
if(serviceGroups.Contains(组间))
{
inter.Ours=正确;
}
}
}

现在,据我所知,您的实例方法没有使用任何成员变量。这使得它们无状态,因此线程安全。然而,在同样的情况下,为了代码清晰和性能上的一点好处,最好将它们标记为“静态”

如果这些实例方法使用的是一个成员变量,那么它们只会像该变量一样具有线程安全性(例如,如果您使用一个简单的列表,它就不会具有线程安全性,您可能会看到奇怪的行为)。长话短说,成员变量是轻松线程安全的敌人

这是我的重构(免责声明,未经测试)。如果您希望提供传入的数据,那么如果您将其作为参数传递,而不是作为成员变量保存,您将保持理智:

更新:您要求提供一种引用只读列表的方法,因此我添加了该方法并删除了静态标记(以便可以共享实例变量)

公共类CustomerClass
{
私有列表someReadOnlyList;
公共客户类(){
List templast=newlist(){“string1”、“string2”};
someReadOnlyList=ArrayList.Synchronized(tempList);
}
公开募捐
{
var groupedData=DataReader.GetSourceData().AsEnumerable().GroupBy(n=>n.Field(“ID”);
并行ForEach(
分组数据,
()=>新建CustomerDataContext(),
(g,ctx)=>
{
var inter=FindOrCreateInteraction(ctx,g.Key);
inter.ID=g.键;
inter.Title=g.First()字段(“Title”);
计算方法属性(参考内部);
返回ctx;
},
ctx=>ctx.SubmitAllChanges());
}
私有交互FindOrCreateInteraction(CustomerDataContext ctx,int-ID)
{
var query=ctx.Interactions.Where(n=>n.Id=Id);
if(query.Any())
{
返回query.Single();
}
其他的
{
var inter=新交互();
ctx.InsertOnSubmit(国际);
返回间隔;
}
}
私有void CalculateSomeProperty(ref inter交互)
{
Console.Writeline(someReadOnlyList[0]);
//做些别的事情
}
}

我似乎找到了答案,在这个过程中,也找到了问题

真正的问题是,本地“变量”(实际上是对象)是否可以被信任进行并发访问。答案是否定的,如果它们碰巧具有未以线程安全方式处理的内部状态,则所有赌注都将被取消。闭包没有帮助,它只是捕获对所述对象的引用

在我的特定情况下-并发读取
IEnumerable
,而不写入它,实际上是线程安全的,因为每次调用
foreach
都包含()
其中()
,等等。会得到一个新的
IEnumerator
,它只在请求它的线程中可见。但是,还必须逐个检查任何其他对象

所以,万岁,我没有锁或同步收集:)

感谢@eb和@Dave,虽然你没有直接回答问题,但你为我指明了正确的方向


如果您对结果感兴趣,这是在我的家用电脑(四核)上运行的
Thread.SpinWait
模拟一行的处理时间。真正的应用程序在本地网络上的双核超线程机器上的性能提高了近2倍(01:03 vs 00:34)

单线程,使用
foreach
。我不知道为什么,但是有相当多的跨核心上下文切换


使用
Parallel.ForEach
,在需要的地方使用线程局部变量解除锁定。

感谢您的回复:)但是,如果我不是在写列表,而是只从列表中读取?第二种方法,
CalculateSomeProperty()
从该
列表中查找一些内容,但从不从中添加或删除。它更容易从整个类中可见,因为其他方法需要它。@VladislavZorov,
ReadOnlyCollection
-因此它看起来是
public class CustomerClass
{

private List<string> someReadOnlyList;

    public CustomerClass(){
      List<string> tempList  = new List<string>() { "string1", "string2" };
      someReadOnlyList = ArrayList.Synchronized(tempList);
    }

    public void Run()
    {
        var groupedData = DataReader.GetSourceData().AsEnumerable().GroupBy(n => n.Field<int>("ID"));

        Parallel.ForEach<IGrouping<int, DataRow>, CustomerDataContext>(
            groupedData,
            () => new CustomerDataContext(),
            (g, _, ctx) =>
            {
                var inter = FindOrCreateInteraction(ctx, g.Key);

                inter.ID = g.Key;
                inter.Title = g.First().Field<string>("Title");

                CalculateSomeProperty(ref inter);

                return ctx;
            },
            ctx => ctx.SubmitAllChanges());
    }

    private Interaction FindOrCreateInteraction(CustomerDataContext ctx, int ID)
    {
        var query = ctx.Interactions.Where(n => n.Id = ID);

        if (query.Any())
        {
            return query.Single();
        }
        else
        {
            var inter = new Interaction();
            ctx.InsertOnSubmit(inter);
            return inter;
        }
    }

    private void CalculateSomeProperty(ref Interaction inter)
    {
        Console.Writeline(someReadOnlyList[0]);
        //do some other stuff
    }
}