#c# #asp.net-core #scheduled-tasks #asp.net-core-hosted-services
#c# #asp.net-core #запланированные задачи #asp.net-core-hosted-services
Вопрос:
Я работаю над проектом, основанным на ASP.NET Ядро 3.1, и я хочу добавить к нему определенную функциональность, чтобы запланировать публикацию публикации в будущем в дату и время, указанные автором сообщения (что-то вроде того, что WordPress делает для запланированных сообщений через свои задания cron). Например, если мы получим эту дату и время от пользователя :
2020-09-07 14:08:07
Затем, как я могу запланировать фоновую задачу для нее, используя размещенные службы для запуска только один раз и для изменения флага в базе данных и сохранения изменений после этого?
Я прочитал несколько статей об этом, но они не указали дату и время, а просто упомянули повторяющиеся задачи каждые 5 секунд и тому подобное с помощью выражений cron, но мне нужно знать, как я могу запланировать фоновую задачу на определенную дату и время?
Заранее благодарю вас.
Комментарии:
1. Я не понимаю, как вы можете избежать периодической проверки. Вы можете абстрагировать ее с помощью Rx или чего-то подобного
2. FluentScheduler является одним из лучших github.com/fluentscheduler/FluentScheduler
3. Альтернативы: hangfire.io
4. Я бы рекомендовал использовать специально созданное решение для этой функциональности, такое как Hangfire или подобное. С ними довольно легко работать.
5. Вот ответ github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore /…
Ответ №1:
Я объединил CrontabSchedule с IHostedService. Приведенная ниже реализация является легковесной (без архитектуры, навязывающей библиотеки) и без опроса.
public class SomeScheduledService: IHostedService
{
private readonly CrontabSchedule _crontabSchedule;
private DateTime _nextRun;
private const string Schedule = "0 0 1 * * *"; // run day at 1 am
private readonly SomeTask _task;
public SomeScheduledService(SomeTask task)
{
_task = Task;
_crontabSchedule = CrontabSchedule.Parse(Schedule, new CrontabSchedule.ParseOptions{IncludingSeconds = true});
_nextRun = _crontabSchedule.GetNextOccurrence(DateTime.Now);
}
public Task StartAsync(CancellationToken cancellationToken)
{
Task.Run(async () =>
{
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(UntilNextExecution(), cancellationToken); // wait until next time
await _task.Execute(); //execute some task
_nextRun = _crontabSchedule.GetNextOccurrence(DateTime.Now);
}
}, cancellationToken);
return Task.CompletedTask;
}
private int UntilNextExecution() => Math.Max(0, (int)_nextRun.Subtract(DateTime.Now).TotalMilliseconds);
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
Комментарии:
1. Небольшое объяснение вашего кода может иметь большое значение…
2. Здесь объясняется практически тот же подход: medium.com/@gtaposh /…
3. @Dejan действительно, я прочитал это. но я решил не опрашивать, а вместо этого ожидать.
4. @PauliusRaila visual studio предложил добавить пакет
NCrontab.Signed
? это библиотека, которую использует ваш код?5. @T3.0 было некоторое время назад. но это выглядит так: github.com/atifaziz/NCrontab
Ответ №2:
После некоторых проб и ошибок я нашел способ запланировать фоновую задачу на определенную дату и время с помощью размещенной службы, как я задал в вопросе, и я сделал это с System.Threading.Timer
и Timespan
вот так:
public class ScheduleTask : IScheduler, IDisposable
{
private Timer _timer;
private IBackgroundTaskQueue TaskQueue { get; }
// Set task to schedule for a specific date and time
public async Task SetAndQueueTaskAsync(ScheduleTypeEnum scheduleType, DateTime scheduleFor, Guid scheduledItemId)
{
// Omitted for simplicity
// ....
TaskQueue.QueueBackgroundWorkItem(SetTimer);
}
// ......
// lines omitted for simplicity
// .....
// Set timer for schedule item
private Task SetTimer(CancellationToken stoppingToken)
{
// ......
// lines omitted for simplicity
// .....
_timer = new Timer(DoWork, null, (item.ScheduledFor - DateTime.UtcNow).Duration(), TimeSpan.Zero);
return Task.CompletedTask;
}
private void DoWork(object state)
{
ScheduledItemChangeState(DateTime.UtcNow).Wait();
}
// Changes after the scheduled time comes
private async Task ScheduledItemChangeState(DateTime scheduleFor)
{
using (var scope = Services.CreateScope())
{
var context =
scope.ServiceProvider
.GetRequiredService<DataContext>();
// Changing some data in database
}
}
public void Dispose()
{
_timer?.Dispose();
}
}
Если вы посмотрите на часть приведенного выше кода, в которой я передал (item.ScheduledFor - DateTime.UtcNow)
третий параметр конструктора класса Timer для инициализации нового его экземпляра, я фактически прошу таймер выполнить определенную работу за определенное время, которое я сохранил как DateTime в item.ScheduledFor
.
Вы можете прочитать больше о фоновых задачах с размещенными службами в ASP.NET Ядро здесь из официальных документов Microsoft:
Чтобы увидеть полную реализацию в моем репозитории Github, в котором есть возможность восстановить запланированные задачи из базы данных после перезапуска сервера, воспользуйтесь следующей ссылкой:
https://github.com/aspian-io/aspian/tree/master/Infrastructure/Schedule
Комментарии:
1. Но это отбрасывает все таймеры после перезапуска приложения, верно?
2. Да, но в методе «ScheduledItemChangeState» вы можете сохранить все задачи в таблице базы данных, и после перезапуска он проверяет, наступает ли время выполнения какой-либо задачи, и запускает их, а после запуска удаляет их из таблицы. Таким образом, вы все равно не потеряете запланированные задачи.
3. @Mateech Вы можете увидеть полную реализацию, которая имеет возможность восстанавливать запланированные задачи из базы данных после перезапуска сервера, в моем репозитории Github по следующей ссылке: github.com/aspian-io/aspian/tree/master/Infrastructure/Schedule
Ответ №3:
Я хочу добавить к ней определенную функциональность, чтобы запланировать публикацию сообщения в будущем в дату и время, указанные автором сообщения.Например, если мы получим эту дату и время от пользователя: 2020-09-07 14:08:07 .
Затем, как я могу запланировать фоновую задачу для нее, используя размещенные службы для запуска только один раз и для изменения флага в базе данных и сохранения изменений после этого?
Кажется, что вы хотели бы выполнить фоновую задачу / задание в указанное пользователем время, чтобы выполнить требование, вы можете попробовать использовать некоторые службы очереди сообщений, такие как Azure Queue Storage, которые позволяют нам указать, как долго сообщение должно быть невидимым для операций удаления из очереди и просмотра, установив visibilityTimeout
.
В то время как пользователь вашего приложения хочет создать новую запись и указать дату публикации, вы можете вставить новое сообщение (с указанным VisibilityTimeout на основе ожидаемой пользователем даты и времени) в очередь, чтобы это новое вставленное сообщение было видно только в указанную дату и время в очереди.
QueueClient theQueue = new QueueClient(connectionString, "mystoragequeue");
if (null != await theQueue.CreateIfNotExistsAsync())
{
//The queue was created...
}
var newPost = "Post Content Here";
var user_specified_datetime = new DateTime(2020, 9, 9, 20, 50, 25);
var datetime_now = DateTime.Now;
TimeSpan duration = user_specified_datetime.Subtract(datetime_now);
await theQueue.SendMessageAsync(newPost, duration, default);
Затем вы можете реализовать фоновую задачу с запуском очереди для извлечения сообщений из очереди и обновления записей вашей базы данных.
Примечание: Эмулятор хранилища Microsoft Azure — это инструмент, который эмулирует службы очереди Azure и т. Д. Для целей локальной разработки и тестирования, вы можете попробовать протестировать код на службах хранения локально, не создавая подписку Azure и не неся никаких затрат.
Комментарии:
1. Спасибо за ваш очень подробный ответ, и на самом деле было бы здорово, если бы я использовал службы очереди сообщений. Но все же по какой-то причине я должен использовать что-то вроде System. . Таймер для этого и с временным интервалом, я думаю, я нашел способ сделать это. Я действительно ценю ваш ответ и описание.
Ответ №4:
Используйте DNTScheduler и установите конкретную дату и время
services.AddDNTScheduler(options =>
{
// DNTScheduler needs a ping service to keep it alive. Set it to false if you don't need it. Its default value is true.
// options.AddPingTask = false;
options.AddScheduledTask<DoBackupTask>(
runAt: utcNow =>
{
var now = utcNow.AddHours(3.5);
return now.Hour == 14 amp;amp; now.Minute == 08 amp;amp; now.Second == 07;
},
order: 1);
});
Комментарии:
1. Тьфу. Это выглядело великолепно, но было устаревшим в пользу… DNTCommon.Web.Core, который делает все виды вещей , которые мне не нужны, с большим количеством зависимостей.