Azure WebJobs与实体框架作业并行

时间:2015-12-09 21:28:34

标签: entity-framework azure azure-webjobs azure-webjobssdk

我有一个使用我的MVC azure web应用程序部署的Azure webjob。这个作业有两个队列触发的功能,一个是获取PDF文件并将其分成单个页面图像,然后是第二个处理各个页面的功能。我在网站和webjob上都使用EF 6和Unity。

使用IJobActivator选项我将DbContext和UoW / Services / Repositories注入webjob没有任何问题。当我尝试并行运行webjob时,问题似乎就出现了。如果我将webjob配置选项设置为config.Queues.BatchSize = 1,一切都很好 - 但速度慢(串行,一次一页)。如果我将BatchSize提升到大于1的任何值,那么我会在&#34的行中出现错误;在先前的异步操作完成之前,在此上下文中启动第二个操作"。由于注入了DbContext,我尝试使用LifetimeManager选项来查看是否存在问题 - 没有变化。错误说我需要确保我的所有EF调用都使用" await" (是异步的),但它们......所以这似乎不是问题。

如果我无法并行运行,那么webjob的用处将会严重减少。有什么想法吗?在webjob中放弃EF和DbContext并使用ADO.Net直接转到数据库会不会更好?

3 个答案:

答案 0 :(得分:1)

错误消息很明显,您无法同时对同一个DbContext进行多次未完成的异步操作。由于您肯定希望保持工作并行性(即不要将BatchSize设置为1),因此您可能只需要为每个作业函数调用使用一个新的DbContext。 DbContext是轻量级的,所以这对你来说不是一个问题。

官方DbContext documentation还提供了有关在WebApps中管理DbContext实例的以下指导:"使用Web应用程序时,请按请求使用上下文实例。" 该指南适用于您的WebJob案例。

答案 1 :(得分:0)

正如我的评论中所提到的,并行性问题的解决方案归结为将DbContext生命周期保持在WebJob的单个方法中,因为EF不是线程安全的。尝试使用Unity和IJobActivator将这些东西保存在更高级别(在方法之外),甚至更改LifetimeManager也没有效果。通过摆脱Unity并在DbContext上的WebJob中创建一个“使用”语句,它保证了一切安全和有效。

生产Azure上的SQL数据库丢失/重置似乎与EF DB初始化程序和迁移有关。我将开始另一个问题,因为它与我原来的问题没有直接关系。

答案 2 :(得分:0)

您是如何配置团结注册的? 我想如果你想注入你的DbContext,你应该只使用一个注册来创建一个每次调用的新实例。每次新消息到达队列时,框架将使用IJobActivator获取依赖项。

我使用Simple Injector

实现了IJobActivator
/// <summary>
/// An activator that uses <see cref="SimpleInjector"/> to return instance of a job type.
/// </summary>
public class SimpleInjectorJobActivator : IJobActivator
{
    /// <summary>
    /// Gets or sets the container to resolve dependencies.
    /// </summary>
    private readonly Container _container;

    /// <summary>
    /// Initialize a new instance of the <see cref="SimpleInjectorJobActivator"/> class.
    /// </summary>
    /// <param name="container">The <see cref="Container"/> to resolve dependencies.</param>
    public SimpleInjectorJobActivator(Container container)
    {
        _container = container;
    }

    /// <summary>
    /// Creates a new instance of a job type.
    /// </summary>
    /// <typeparam name="T">The job type.</typeparam>
    /// <returns>
    /// A new instance of the job type.
    /// </returns>
    public T CreateInstance<T>()
    {
        return (T)_container.GetInstance(typeof(T));
    }
}

如果您为每个队列触发创建一个作业处理器,您可以创建两个类:

public class MyJobProcessor1
{
    private readonly MyDbContext _myDbContext;

    public MyJobProcessor1(MyDbContext myDbContext)
    {
        _myDbContext = myDbContext;
    }


    public void Run([QueueTrigger("my-queue1")] BrokeredMessage message)
    {

    }
}

我的主要职能是:

static void Main()
{
    // Register my dependency, by default simple injector create a new instance per call
    var container = new Container();
    container.Register(() => new MyDbContext());
    container.Verify();

    // Configure the job host
    var config = new JobHostConfiguration
    {
        JobActivator = new SimpleInjectorJobActivator(container)
    };
    // Run the host
    var host = new JobHost(config);
    host.RunAndBlock();
}

每次收到新邮件时,IJobActivator都会返回一个在我的IoC容器中注册的DbContext的新实例。