繁体   English   中英

TPL Parallel.ForEach中的每个线程实例对象

[英]Per-thread instance object in TPL Parallel.ForEach

是否有TPL语法允许您将对象从池中注入到任务中,以便一个线程一次只能使用一个对象? 甚至更好-仅由同一个线程使用?

使用范例

假设我想创建10个线程来打开10个文件: 1.txt2.txt3.txt ... 10.txt并将500 000个后续数字随机写入这些文件。

我可以做这个:

ConcurrentQueue<int> objs = new ConcurrentQueue<int>(); // 500000 numbers go here
Task[] tasks = Enumerable.Range(1, 10)
    .Select(i =>
    {
        return Task.Factory.StartNew(() => 
        {
            using (var f = File.Open($"{i}.txt"))
            {
                using (var wr = StreamWriter(f))
                {
                    while (objs.TryDequeue(out int obj))
                    {
                        wr.WriteLine(obj);
                    }
                }
            }
        }
    })
    .ToArray();
Task.WaitAll(tasks);

但是,是否仅使用TPL就可以在不利用并发集合的情况下提供相同的行为?

如果将最后两个编辑以外的所有内容都删除,那会更好。

如果问题是Can you pass an object per task (not thread) when using Parallel.是否Can you pass an object per task (not thread) when using Parallel. 答案是:是的,你可以通过任何重载接受本地状态,即有TLocal型像这一个

public static ParallelLoopResult ForEach<TSource, TLocal>(
    IEnumerable<TSource> source,
    Func<TLocal> localInit,
    Func<TSource, ParallelLoopState, TLocal, TLocal> body,
    Action<TLocal> localFinally
)

Parallel.For不使用线程。 它对数据进行分区,并为每个分区创建一个任务。 每个任务最终都会处理分区的所有数据。 通常, Parallel使用与内核一样多的任务。 它还使用当前线程进行处理,这就是为什么它似乎阻塞了当前线程的原因。 并非如此,它开始用于处理其中一个分区。

处理本地数据的函数使您可以生成初始本地值,并将其传递给每个body调用。 本地数据的所有重载都要求body重新调整(可能已修改的)数据,因此Parallel本身不必存储它。 由于Parallel. ,这是必不可少的Parallel. 可以终止并重新启动任务。 如果必须跟踪本地数据,它将无法轻松或高效地做到这一点。

对于此特定示例,并绕过ORM不适合批量操作的事实,尤其是在处理成千上万个对象时, localInit应该创建一个新会话。 body应使用并返回该会话,而最后, localFinally应该将其处置。

var mySessionFactory
var myData=....;
Parallel.ForEach(
    myData,
    ()=>CreateSession(),
    (record,state,session)=>{
        //process the data etc.
        return session;
    },
    (session)=>session.Dispose()
);

不过,还有一些警告。 NH将更改保留在内存中,直到将其清除并清除缓存。 这将导致内存问题。 一种解决方案是保持计数并定期刷新数据。 代替会话,状态可以是(int counter,Session session) tupple:

Parallel.ForEach(
    myData,
    ()=>(counter:0,session:CreateSession()),
    (record,state,localData)=>{
        var (counter,session)=localData;
        //process the data etc.
        if (counter % 1000 ==0)
        {
            session.Flush();
            session.Clear();
        }
        return (++counter,session);
    },
    data=>data.session.Dispose()
);

更好的解决方案是预先批处理对象,以使循环可以在IEnumerable<MyRecord[]>数组上运行,而不是IEnumerable<MyRecord> 结合批处理语句,这将减少ORM对批量操作施加的性能损失。

编写Batch方法并不难,但是MoreLinq已经提供了一个方法,可以作为源代码或NuGet包使用:

var myBatches=myData.Batch(1000);
Parallel.ForEach(
    myBatches,
    ()=>CreateSession(),
    (records,state,session)=>{

        foreach(var record in records)
        {
            //process the data etc.
            session.Save(record);                
        }
        session.Flush();
        session.Clear();
        return session;
    },
    data=>data.session.Dispose()
);

不,那里没有。

最接近的解决方案是手动创建N个线程(使用TaskParallel.For / Parallel.ForEach ),并使用ConcurrentQueue安全地分发数据。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM