C# 并行的实例成员。Foreach循环与线程安全

C# 并行的实例成员。Foreach循环与线程安全,c#,multithreading,parallel-processing,thread-safety,locking,C#,Multithreading,Parallel Processing,Thread Safety,Locking,我将有以下问题,它们以某种方式相互关联,并与下面的代码相关(简化-参见注释): 在上面的代码中,数据处理器使用Parallel.Foreach循环来加速ProcessItem方法中的一些繁重处理(与问题无关)。处理后,通过DataUpdater类在数据库中更新该项 问题是: 当使用数据库更新程序作为DataProcessor的实例成员时,ProcessItem调用线程安全吗 如果不是,那么在ProcessItem方法中调用dataUpdater.Update时添加lock(obj)是否解决了线程

我将有以下问题,它们以某种方式相互关联,并与下面的代码相关(简化-参见注释):

在上面的代码中,数据处理器使用
Parallel.Foreach
循环来加速
ProcessItem
方法中的一些繁重处理(与问题无关)。处理后,通过DataUpdater类在数据库中更新该项

问题是:

  • 当使用数据库更新程序作为
    DataProcessor
    的实例成员时,
    ProcessItem
    调用线程安全吗
  • 如果不是,那么在
    ProcessItem
    方法中调用
    dataUpdater.Update
    时添加
    lock(obj)
    是否解决了线程安全问题,前提是该方法中的其余处理不需要是线程安全的
  • DataUpdater
    (及其方法)是否应该改为静态?这会消除线程安全问题吗
  • 是否应该在循环中创建
    DataUpdater
    的新实例,并将其作为参数传递给
    ProcessItem
  • 编辑

    将代码添加到
    Update
    方法

    当使用数据库更新程序作为DataProcessor的实例成员时,ProcessItem调用是线程安全的吗

    无法确定,因为您没有向我们显示
    Update()
    的确切功能,也没有显示
    IDatabaseConnection
    的方法是否可以安全地同时调用多个线程,但是这很可能不是线程安全的

    如果不是,那么在ProcessItem方法中围绕调用dataUpdater.Update添加锁(obj)是否解决了线程安全问题,前提是该方法中的其余处理不需要是线程安全的

    这可能会使它成为线程安全的,但是您需要确保
    IDatabaseConnection
    是线程安全的,或者在循环运行时不会在该循环之外的程序中的任何其他地方同时使用

    类DataUpdater(及其方法)应该改为静态的吗?这会消除线程安全问题吗

    不,使某个东西成为静态并不能神奇地使某个东西成为线程安全的,如果它是静态的或不是静态的,则是方法中的代码使它成为安全的

    是否应该在循环中创建DataUpdater的新实例,并将其作为参数传递给ProcessItem

    是和否,
    Parallel.ForEach
    具有特殊委托,这些委托发生在每个线程的开始和结束处,该线程包含一个线程本地对象,并且该对象在每次调用的线程中都会被重用。您应该在那里创建新的更新程序

    在下面的示例中,我做了一些小假设,
    someConnection
    不是线程安全的,但提供了一个
    .Clone()
    方法,该方法创建了一个新连接,可以与旧连接同时使用。我还假设在
    DataUpdater
    上有一个
    .Close()
    方法,可以调用该方法来关闭传入的连接

    public void Process()
    {
        Parallel.ForEach(itemCollection,
            //This is called at the creation of a thread, may be called on many threads at once.
            () =>  
            {
                lock(someConnection)
                {
                    var newConnection = someConnection.Clone();
                    return new DataUpdater(newConnection)
                }
            };
            (item, loopState, dataUpdater) => //dataUpdater is the thread local copy
            {
                ProcessItem(item, dataUpdater);
    
                // (commented as not relevant to question)
                // if (mustStop()) 
                // {
                //     loopState.Break();
                // }
    
                //This will get passed in to the next item or the cleanup method.
                return dataUpdater;
             }
            //This is called at the end of a thread after it has finished processing items.
            , (dataUpdater) => dataUpdater.Close() 
            );
    }
    
    private void ProcessItem(Item item, DataUpdater dataUpdater)
    {
        // some processing...
    
        dataUpdater.Update(item);
    }
    

    附录A:

    ForEach内部工作的简化示例

    public static ParallelLoopResult ForEach<TSource, TLocal>(IEnumerable<TSource> source, 
                           Func<TLocal> localInit,
                           Func<TSource, ParallelLoopState, TLocal, TLocal> body,
                           Action<TLocal> localFinally)
    {
        ParallelLoopState[] loopStates;
        IEnumerable<TSource>[] jobBatches;
        // (SNIP)
        Task.Run(() => WorkerThread(jobBatchs[0], localInit, body, localFinally, loopStates[0]))
        Task.Run(() => WorkerThread(jobBatchs[1], localInit, body, localFinally, loopStates[1]))
        // (SNIP)
    }
    
    private static void WorkerThread<TSource, TLocal>(IEnumerable<TSource> jobBatch,
                           Func<TLocal> localInit,
                           Func<TSource, ParallelLoopState, TLocal, TLocal> body,
                           Action<TLocal> localFinally,
                           ParallelLoopState loopState)
    {
        TLocal localData = localInit();
        foreach(TSource data in jobBatch)
        {
            if(loopState.ShouldExitCurrentIteration)
                break;
            localData = body(data, loopState, localData);
        }
        localFinally(localData);
    }
    
    ForEach的公共静态并行循环结果(IEnumerable源代码,
    Func localInit,
    职能机构,
    行动(最后)
    {
    ParallelLoopState[]循环状态;
    IEnumerable[]作业批次;
    //(剪报)
    Run(()=>WorkerThread(jobBatchs[0],localnit,body,localFinally,loopStates[0]))
    Run(()=>WorkerThread(jobBatchs[1],localnit,body,localFinally,loopStates[1]))
    //(剪报)
    }
    私有静态void WorkerThread(IEnumerable jobBatch,
    Func localInit,
    职能机构,
    最后,,
    并行循环状态(loopState)
    {
    TLocal localData=localnit();
    foreach(jobBatch中的源数据)
    {
    if(loopState.ShouldExitCurrentIteration)
    打破
    localData=body(数据、循环状态、localData);
    }
    localFinally(localData);
    }
    
    当使用数据库更新程序作为DataProcessor的实例成员时,ProcessItem调用是线程安全的吗

    无法确定,因为您没有向我们显示
    Update()
    的确切功能,也没有显示
    IDatabaseConnection
    的方法是否可以安全地同时调用多个线程,但是这很可能不是线程安全的

    如果不是,那么在ProcessItem方法中围绕调用dataUpdater.Update添加锁(obj)是否解决了线程安全问题,前提是该方法中的其余处理不需要是线程安全的

    这可能会使它成为线程安全的,但是您需要确保
    IDatabaseConnection
    是线程安全的,或者在循环运行时不会在该循环之外的程序中的任何其他地方同时使用

    类DataUpdater(及其方法)应该改为静态的吗?这会消除线程安全问题吗

    不,使某个东西成为静态并不能神奇地使某个东西成为线程安全的,如果它是静态的或不是静态的,则是方法中的代码使它成为安全的

    是否应该在循环中创建DataUpdater的新实例,并将其作为参数传递给ProcessItem

    是和否,
    Parallel.ForEach
    具有特殊委托,这些委托发生在每个线程的开始和结束处,该线程包含一个线程本地对象,并且该对象在每次调用的线程中都会被重用。您应该在那里创建新的更新程序

    在下面的示例中,我做了一些小假设,
    someConnection
    不是线程安全的,但提供了一个
    .Clone()
    方法,该方法创建了一个新连接,可以与旧连接同时使用。我还假设在
    DataUpdater
    上有一个
    .Close()
    方法
    public static ParallelLoopResult ForEach<TSource, TLocal>(IEnumerable<TSource> source, 
                           Func<TLocal> localInit,
                           Func<TSource, ParallelLoopState, TLocal, TLocal> body,
                           Action<TLocal> localFinally)
    {
        ParallelLoopState[] loopStates;
        IEnumerable<TSource>[] jobBatches;
        // (SNIP)
        Task.Run(() => WorkerThread(jobBatchs[0], localInit, body, localFinally, loopStates[0]))
        Task.Run(() => WorkerThread(jobBatchs[1], localInit, body, localFinally, loopStates[1]))
        // (SNIP)
    }
    
    private static void WorkerThread<TSource, TLocal>(IEnumerable<TSource> jobBatch,
                           Func<TLocal> localInit,
                           Func<TSource, ParallelLoopState, TLocal, TLocal> body,
                           Action<TLocal> localFinally,
                           ParallelLoopState loopState)
    {
        TLocal localData = localInit();
        foreach(TSource data in jobBatch)
        {
            if(loopState.ShouldExitCurrentIteration)
                break;
            localData = body(data, loopState, localData);
        }
        localFinally(localData);
    }