此功能是Hangfire Pro套装的一部分
批处理允许您自动创建一堆后台作业。这意味着,如果在创建后台作业期间出现异常,则不会处理任何作业。假设你想给你的客户发送1000封电子邮件,他们真的很想收到这些电子邮件。这是老办法:
for (var i = 0; i < 1000; i++)
{
BackgroundJob.Enqueue(() => SendEmail(i));
// What to do on exception?
}
但是,如果i==500上的存储变得不可用怎么办?可能已经发送了500封电子邮件,因为工作线程一旦创建就会接收并处理作业。如果重新执行此代码,某些客户端可能会收到令人讨厌的重复代码。所以,如果你想正确处理这个问题,你应该编写更多的代码来跟踪发送的电子邮件。
但这里有一个简单得多的方法:
BatchJob.StartNew(x =>
{
for (var i = 0; i < 1000; i++)
{
x.Enqueue(() => SendEmail(i));
}
});
在出现异常的情况下,您可能会向用户显示一个错误,只需在几分钟后请求重试她的操作。无需其他代码!
批次在Hangfire.Pro软件包中可用,您可以像往常一样使用NuGet package Manager Console窗口安装它:
Install-Package Hangfire.Pro
批处理需要添加一些附加的作业筛选器、仪表板中的一些新页面以及一些新的导航菜单项。但由于新的GlobalConfiguration类,它现在就像一个方法调用一样简单:
GlobalConfiguration.Configuration.UseBatches();
有限的存储支持。目前仅支持官方的Hangfire.InMemory、Hangfire.SqlServer和Hangfire.Pro.Redis作业存储实现。批次没有什么特别之处,但应该实施一些新的存储方法。
如果批处理成功,默认的批处理作业到期/保留时间为7天,但您可以在调用UseBatches方法时进行配置:
GlobalConfiguration.Configuration.UseBatches(TimeSpan.FromDays(2));
连续允许您将多个批次链接在一起。一旦父批处理的所有后台作业完成,它们将被执行。考虑前面的示例,您有1000封电子邮件要发送。如果您想在发送后执行最终操作,只需添加一个继续:
var id1 = BatchJob.StartNew(/* for (var i = 0; i < 1000... */);
var id2 = BatchJob.ContinueBatchWith(id1, x =>
{
x.Enqueue(() => MarkCampaignFinished());
x.Enqueue(() => NotifyAdministrator());
});
因此,批处理和批处理延续允许您定义工作流并配置将并行执行的操作。这对于繁重的计算方法非常有用,因为它们可以分布到不同的机器。
创建操作不会限制您仅在排队状态下创建作业。您可以安排稍后执行的作业、添加延续、将延续添加到延续等。。
var batchId = BatchJob.StartNew(x =>
{
x.Enqueue(() => Console.Write("1a... "));
var id1 = x.Schedule(() => Console.Write("1b... "), TimeSpan.FromSeconds(1));
var id2 = x.ContinueJobWith(id1, () => Console.Write("2... "));
x.ContinueJobWith(id2, () => Console.Write("3... "));
});
BatchJob.ContinueBatchWith(batchId, x =>
{
x.Enqueue(() => Console.WriteLine("4..."));
});
从2.0版开始,批处理可以由其他批处理组成,而不仅仅是后台作业。外部批次被称为父批次,内部批次是子批次(对于延续,它是先行/延续关系)。您可以在单个批处理中将批处理和后台作业混合在一起。
BatchJob.StartNew(parent =>
{
parent.Enqueue(() => Console.WriteLine("First"));
parent.StartNew(child => child.Enqueue(() => Console.WriteLine("Second")));
});
支持多个嵌套级别,因此每个子批又可以成为另一批的父批,从而允许您创建非常复杂的批层次结构。
BatchJob.StartNew(batch1 =>
{
batch1.StartNew(batch2 =>
{
batch2.StartNew(batch3 => batch3.Enqueue(() => Console.WriteLine("Nested")));
});
});
整个层次结构(包括父批次、其所有子批次和后台作业)都在一个事务中创建。因此,此功能不仅允许您在单个仪表板页面上看到一组相关的批处理,还可以原子地创建多个批处理。
var antecedentId = BatchJob.StartNew(batch =>
{
batch.StartNew(inner => inner.Enqueue(() => Console.WriteLine("First")));
batch.StartNew(inner => inner.Enqueue(() => Console.WriteLine("Second")));
});
如果父批处理的所有后台作业和批处理都成功,则父批处理成功。如果父批处理的所有批处理和后台作业都处于最终状态,则父批处理已完成。因此,您可以为多个批次创建延续,而不仅仅是为单个批次创建延续。批处理延续还支持嵌套功能。
BatchJob.ContinueBatchWith(antecedentId, continuation =>
{
continuation.StartNew(inner => inner.Enqueue(() => Console.WriteLine("First")));
continuation.StartNew(inner => inner.Enqueue(() => Console.WriteLine("Second")));
});
从Hangfire.Pro2.1.0开始,还可以在批处理和后台作业中使用独立和嵌套的批处理延续。
BatchJob.StartNew(parent =>
{
var nested1 = parent.StartNew(nested =>
{
nested.Enqueue(() => Console.WriteLine("Nested 1"));
});
var nested2 = parent.ContinueBatchWith(nested1, () => Console.WriteLine("Nested 2"));
var nested3 = parent.ContinueJobWith(nested2, nested =>
{
nested.Enqueue(() => Console.WriteLine("Nested 3"));
});
string nested5 = null;
var nested4 = parent.ContinueBatchWith(nested3, nested =>
{
nested5 = nested.Enqueue(() => Console.WriteLine("Nested 4"));
});
parent.ContinueJobWith(nested5, () => Console.WriteLine("Nested 5"));
});
这是2.0版提供的另一个有趣的功能,它允许您通过将新的后台作业和子批次附加到现有批次来修改现有批次。您可以在任何状态下添加后台作业,也可以添加嵌套批次。如果已完成修改的批次,则将其移回开始状态。
var batchId = BatchJob.StartNew(batch => batch.Enqueue(() => Console.WriteLine("First")));
BatchJob.Attach(batchId, batch => batch.Enqueue(() => Console.WriteLine("Second")));
如果您希望并行处理一系列记录,然后执行一个延续,则此功能会有所帮助。以前,您必须生成非常长的延续链,并且很难调试它们。因此,您可以创建结构,然后修改批处理。
var batchId = BatchJob.StartNew(batch => batch.Enqueue(() => ProcessHugeList(batch.Id, ListId)));
BatchJob.ContinueBatchWith(batchId, batch => batch.Enqueue(() => SendNotification(ListId)));
// ProcessHugeList
BatchJob.Attach(batchId, batch =>
{
foreach (var record in records)
{
batch.Enqueue(() => ProcessRecord(ListId, record.Id)));
}
});
可以在没有任何后台作业的情况下创建批。最初,此类空批次被视为已完成,一旦添加了一些后台作业或子批次,它们就会将一个批次移动到已启动状态(或移动到另一个批次,具体取决于其状态)。
var batchId = BatchJob.StartNew(batch => {});
BatchJob.Attach(batchId, batch => batch.Enqueue(() => Console.WriteLine("Hello, world!")));
从2.0版开始,可以使用常规后台作业继续批处理,而无需创建仅由单个后台作业组成的批处理。不幸的是,我们不能为静态类添加扩展方法,所以让我们先创建一个客户端。
var backgroundJob = new BackgroundJobClient();
var batchId = BatchJob.StartNew(/* ... */);
backgroundJob.ContinueBatchWith(batchId, () => Console.WriteLine("Continuation"));
您可以以其他方式使用新功能,并为常规后台作业创建批处理延续。因此,您可以自由定义工作流,其中同步操作由一组并行工作继续,然后继续返回同步方法。
var jobId = BackgroundJob.Enqueue(() => Console.WriteLine("Antecedent"));
BatchJob.ContinueJobWith(jobId, batch => batch.Enqueue(() => Console.WriteLine("Continuation")));
如果您想停止执行具有数百万后台作业的批处理,而不是问题,您可以调用Cancel方法,或单击仪表板中的相应按钮。
var batchId = BatchJob.StartNew(/* a lot of jobs */);
BatchJob.Cancel(batchId);
此方法不会遍历所有作业,它只是设置批处理的属性。当后台作业即将执行时,作业筛选器检查批处理状态,如果批处理已取消,则将作业移动到“已删除”状态。