Cas*_*ter 5 c# parallel-processing multithreading entity-framework entity-framework-6
我现在正在签订一份合同,以提高使用 EF 6 作为 ORM 的现代 SaaS SPA Web 应用程序的后端服务的性能。我建议的第一件事是向当前运行单线程的后端服务引入一些多线程。首席软件工程师表示我们不能这样做,因为 EF 6 不是线程安全的。
我不是实体框架方面的专家。我选择的 ORM 是 DevExpress 的 XPO,并且我已经完成了与下面建议的类似的操作,使用该 ORM 没有出现问题。使用 EF 6 这种模式本质上不安全吗?
int[] ids;
using(var db = new ApplicationDbContext())
{
// query to surface id's of records representing work to be done
ids = GetIdsOfRecordsRepresentingSomeTask(db);
}
Parallel.ForEach(ids, id => {
using(var db = new ApplicationDbContext())
{
var processor = new SomeTaskProcessor(db, id);
processor.ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords();
db.SaveChanges();
}
});
Run Code Online (Sandbox Code Playgroud)
我对此进行了研究,并且我同意 DbContext 不是线程安全的。我建议的模式确实使用多个线程,但单个 DbContext 仅由单个线程以单线程方式访问。领导告诉我,DbContext 本质上是一个单例,这段代码最终会弄乱数据库。我找不到任何东西来支持这个说法。这件事上的领导正确吗?
谢谢
您的模式是线程安全的。但是,至少对于 SQL Server 而言,如果并发性太高,您会发现总吞吐量会随着数据库资源争用的增加而下降。
理论上,Parallel.ForEach 优化了线程数量,但在实践中,我发现它在我的应用程序中允许过多的并发性。
您可以使用ParallelOptions可选参数控制并发。测试您的用例并查看默认并发是否适合您。
您的评论:请记住,无论如何,现在我们正在讨论上面代码中的 100 个 id,其中大多数 id 代表的工作不会最终对数据库进行任何更改,并且寿命很短,而满手可能需要分钟即可完成并最终将 10 条新记录添加到数据库中。您会立即推荐什么 MaxDegreesOfParallelism 值?
根据您的一般描述,可能是 2-3,但这取决于数据库密集程度ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords(与执行 CPU 密集型活动或等待来自文件、Web 服务调用等的 IO 相比)。除此之外,如果该方法主要执行数据库任务,您可能会出现锁定争用或压垮您的 IO 子系统(磁盘)。在您的环境中进行测试以确定。
也许值得探索一下为什么ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords给定的 ID 需要这么长时间才能完成。
更新
下面是一些测试代码,用于演示线程不会相互阻塞并且确实可以并发运行。为了简单起见,我删除了 DbContext 部分,因为它不会影响线程问题。
class SomeTaskProcessor
{
static Random rng = new Random();
public int Id { get; private set; }
public SomeTaskProcessor(int id) { Id = id; }
public void ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords()
{
Console.WriteLine($"Starting ID {Id}");
System.Threading.Thread.Sleep(rng.Next(1000));
Console.WriteLine($"Completing ID {Id}");
}
}
class Program
{
static void Main(string[] args)
{
int[] ids = Enumerable.Range(1, 100).ToArray();
Parallel.ForEach(ids, id => {
var processor = new SomeTaskProcessor(id);
processor.ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords();
});
}
}
Run Code Online (Sandbox Code Playgroud)