Asp.Net Core API Backend ve Xamarin.Forms İle Kelime Oyunu Bölüm 3 (Asp.Net Core IHostedService ve BackgroundService ile Background Tasks)
Selamlar,
Bu yazım sizlere Asp.Net Core projenizde Background service yazmanın yöntemlerinden ve bizim yazdığımız oyun için bunu nasıl kullandığımızdan bahsetmek isiyorum.
Öncelikle bizim nasıl kullandığımızdan önce bir asp.net core projenizde arka planda belli zaman aralıklarında bir şeyler yapmak istediğiniz de neler yapabilirsiniz buna bakalım.
Bunun için başvuracağınız ilk arkadaş IHostedService.
Microsoft.Extensions.Hosting paketinde olan ve zaten Microsoft.AspNetCore.App metapackage ı ile elimize çoktan geçmiş olan bu arkadaş iki metot içeren bir interface
namespace Microsoft.Extensions.Hosting { // // Summary: // Defines methods for objects that are managed by the host. public interface IHostedService { // // Summary: // Triggered when the application host is ready to start the service. // // Parameters: // cancellationToken: // Indicates that the start process has been aborted. Task StartAsync(CancellationToken cancellationToken); // // Summary: // Triggered when the application host is performing a graceful shutdown. // // Parameters: // cancellationToken: // Indicates that the shutdown process should no longer be graceful. Task StopAsync(CancellationToken cancellationToken); } }
StartAsync ve StopAsync bu kadar.
WebHostBuilder (WebHost – IWebHostBuilder) ile uygulamanız ayağa kalkıyor ise bu StartAsync metodu server ayağa kalktıktan hemen sonra (IApplicationLifetime.ApplicationStarted tetiklendikten sonra) tetikleniyor.
GenericHost (HostBuilder) ile uygulamanızı host ediyor iseniz bu sefer de IApplicationLifetime.ApplicationStarted metodu tetiklenmeden hemen önce bu StartAsync metodumuz tetikleniyor.
Aynı şekilde StopAsync de host shutdown olduktan sonra tetikleniyor. Burada IDisposable interface ini implemente etmek ve içeride kullandığımız diğer sınıfları da (ör: Timer gibi) burada dispose etmekte fayda var.
Örnek bir kullanım görelim.
internal class TimedHostedService : IHostedService, IDisposable { private readonly ILogger _logger; private Timer _timer; public TimedHostedService(ILogger logger) { _logger = logger; } public Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Timed Background Service is starting."); _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5)); return Task.CompletedTask; } private void DoWork(object state) { _logger.LogInformation("Timed Background Service is working."); } public Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("Timed Background Service is stopping."); _timer?.Change(Timeout.Infinite, 0); return Task.CompletedTask; } public void Dispose() { _timer?.Dispose(); } }
Yukarıda bir timer vasıtası ile 5 sn de bir log atıcak basit bir IHostedService implementastonu görüyoruz. shutdown olduğunda timer infinite state e alınıp dispose implementasyonunda da dispose ediliyor.
Peki bunun dışında muhemelen ihtiyacımız olacak olan bir detaydan bahsetmek isterim. Bir IHostedService imizde uygulamamıza Scoped lifetime ına sahip olarak register etmiş olduğumuz bir service kullanmak istersek, bunun için scope default olarak üretilmiyor, bunu bizim manuel yapmamız gerekiyor. Aşağıdaki gibi..
private void DoWork()
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService();
scopedProcessingService.DoWork();
}
}
Asp.Net Core 2.0 ile gelen IHostedService in 2.1 de daha kolay kullanımı için bir arkadaş daha var. Bu da BackgroundService, IHostedService implementasyonunu baştan sona yapmak istemezseniz bu arkadaşı Asp.Net Core 2.1 den sonra kullanmaya çekinmeyiniz, birçok senaryoda işinizi görecektir. Bu sayede sadece ExecuteAsync metodunu doldurmanız yeterli. Default olarak CancellationToken timeout süresi 5 sn. ama bu süreyide değiştirmemiz mümkün.
public class MyBackgroundService: BackgroundService
{
private readonly ILogger _logger;
public MyBackgroundService(ILogger logger)
{
...
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogDebug($"Service is starting.");
stoppingToken.Register(() =>
_logger.LogDebug($"background task is stopping."));
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogDebug($"task doing background work.");
await Task.Delay(_settings.CheckUpdateTime, stoppingToken);
}
_logger.LogDebug($"background task is stopping.");
}
...
}
Cancellation token time out süresini aşağıdaki gibi değiştirebilirsiniz.
WebHost.CreateDefaultBuilder(args) .UseShutdownTimeout(TimeSpan.FromSeconds(10));
Peki biz bu uygulamada bu background service i ne için ve nasıl kullandık kısmına gelirsek. Bizim asıl ihtiyacımız olan şey, belli bir sürede birşeyler çalıştırmanın yanında o belli bir sürenin kendisi idi 🙂 Her bir oyun için 60 yada 90 saniyelik sürelerimiz var. Ve bu süreleri HostedService mizde sürekli olarak geri sayıyoruz. oyun bitikten sonra bazı işlemler için 30 saniyelik te bir bekleme süresi var. Yani bir kullanıcı oyunu açtığında ve canlı oyuna katılmak istediğinde, o an oyun oynanıyor ise oyunun bitmesine kaç saniye kaldığını yada oyun bitmiş ve 30 sn lik bekleme süresinde isek de yeni oyunun başlamasına kaç saniye kaldığını göstermemiz gerekti.
Aşağıdaki resimde ki gibi.
Kullanıcı oyuna katılmak istediğinde ona hosted service in o an geri sayarken ki saniyenisi dönüyoruz, signalr ile. Ve kalan süreyi bir kere client aldıktan sonra artık kendi cihazındaki timer ile sayma işlemi devam ediyor.
Bizim HostedService miz şu şekilde.
internal class TimedHostedService : IHostedService, IDisposable { private readonly ILogger _logger; private readonly IHubContext _hubContext; public IServiceProvider Services { get; } private Timer _timer; public TimedHostedService(ILogger logger, IServiceProvider services, IHubContext hubContext) { _logger = logger; _hubContext = hubContext; Services = services; } public Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Timed Background Service is starting."); _timer = new Timer(GetNewGameAndBroadcast, null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); return Task.CompletedTask; } private async void GetNewGameAndBroadcast(object state) { if (TimerService.WaitDuration == 25) { _logger.LogInformation("Timed Background Service is working."); using (var scope = Services.CreateScope()) { var gameService = scope.ServiceProvider.GetRequiredService(); var game = await gameService.NewGame(4); var gameJson = JsonConvert.SerializeObject(game); TimerService.gameJson = gameJson; try { await _hubContext.Clients.All.SendAsync("GameReceived", gameJson); } catch (Exception ex) { ... } } } } public Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("Timed Background Service is stopping."); _timer?.Change(Timeout.Infinite, 0); return Task.CompletedTask; } public void Dispose() { _timer?.Dispose(); } }
Burada her bir saniyede ilgili metodu çalıştırıp eğer oyun bekleme süresinde ise zaten online olan kullanıcılar için yeni oyunu 25 saniye kala üretip IHubContext i kullanarak tüm kullanıcılara broadcast ediyoruz, bu durumda her online olan kişi süre bittiğinde aynı oyuna başlıyor, oyun sırasında dahil olanlarda bu oluşturulmuş olan oyunu alıp kalan saniyeden oyuna devam ediyorlar.
Bu yazımda bahsetmek istediklerim bu kadar. Bir sonraki yazımda asıl oyun için gerekli olan Grid i nasıl oluşturduk neler yaptık bundan bahsedeceğim.
Bir sonraki yazımda görüşmek üzere.