How is token.IsCancellationRequested false when I hit CTRL C? - c#

The TLDR: Hitting CTRL C works for the initial worker service code, but adding in any sort of complexity seems to break the functionality.
If I create a blank C# worker service project, I get the following code:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1000, stoppingToken);
}
}
}
Playing around with this, if I run from the command line, and then hit CTRL C, I see the message:
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
And all is well. The app exits. As I began working on this project, I added in a TCP client, and I was always careful to keep this basic framework intact.
However, in testing it seems like I could sometimes hit CTRL C, see the application shutting down message, but the app does not exit. At first it seemed like reading IsCancellationRequested was causing the event to reset, so I changed to a class level variable that periodically reads that bool and used the bool everywhere else for the check. I thought that fixed the problem, but it was probably just a lucky coincidence. Now, I can firmly say the app does not exit when I hit CTRL C. If I add in a breakpoint, I can see when the code reaches the check that IsCancellationRequested returns false, even though I hit CTRL C and saw the app exiting message before the breakpoint was hit.
Does this thing even work? If no one has any answers, I will see if I can create a minimal example that exhibits the behavior. I tested the fresh worker project code I posted and it definitely works as expected. I don't see how my additions could cause any problems.
EDIT:
Here is an extremely minimal addition that will reproduce this bug (?). Connecting to 127.0.0.1 will of course fail, but the cancellation info is gone by the time that exception is handled:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
try
{
using (var client = new TcpClient("127.0.0.1", 8090))
using (var stream = client.GetStream())
{
_logger.LogInformation("connected to socket");
}
}
catch (Exception ex)
{
_logger.LogError("Exception in ExecuteAsync {mess}, {trace}", ex.Message, ex.StackTrace);
}
}
}
}
I can't get the log to stay in a code block in my post here, but it just shows application shutting down, and the loop never exits.
EDIT 2:
How is this not a bug? Calling synchronous code causing the rest of the program to behave improperly? Per Stephen's suggestion, I made the following change and now it properly exits:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
try
{
using (var client = new TcpClient())
{
await client.ConnectAsync("127.0.0.1", 8090);
using (var stream = client.GetStream())
{
_logger.LogInformation("connected to socket");
}
}
}
catch (Exception ex)
{
_logger.LogError("Exception in ExecuteAsync {mess}, {trace}", ex.Message, ex.StackTrace);
}
}
}
}
EDIT 3:
During development and testing, I tested against Netcat running on a Linux system. When I did the final deploy today, the actual socket being connected to is a SICK controller. The async code did not work. I reverted it to the version I started with and it now works. It will run as a service that never stops, so I guess I don't care that it doesn't exit properly. But I am having some doubts about C# and async that I didn't have before. Yay.

Related

Background Service in Singleton Scope

in my rested-web-service project, i need to check database in one hour intervals, this method should continue to work all the time, even if there is not user, and this method is single per hosted server.(some thing like cron in linux but i will host is iis)
i have created a hostedService using microsft docs,
with merely modifying the execute intervals (from 5 sec to 1 hours),
**but problem occure after i deploy webservice solution in iis,this hosted servicerun if swagger page is open when i close browser or not calling rest-service, there is no logs in database
another problem is if a open if i open swagger ui and call webservices from mobile-client there is multiple logs instead of only one per hour.**
i bellive there is two problem here, first TimedHostedService scope is not singleton and it is created per request(scoped) and second problem is application is not kept alive where there is not any session in swagger-ui (or there is no user for werb services).
i hava tried creating Async Method for this purpose that method call-it self with task.delay(onHour)
but this didnt work either.
public class TimedHostedService : IHostedService, IDisposable
{
private int executionCount = 0;
private readonly ILogger<TimedHostedService> _logger;
private Timer? _timer = null;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromHours(1));
return Task.CompletedTask;
}
private void DoWork(object? state)
{
var count = Interlocked.Increment(ref executionCount);
_logger.LogInformation(
"Timed Hosted Service is working. Count: {Count}", count);
}
public Task StopAsync(CancellationToken stoppingToken)
{
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}

ASP.Net Core Background Service with Task Queue, does it need a Task.Delay?

I have followed the Microsoft documentation on how to implement a BackgroundService with a task queue, but I noticed there's no Task.Delay in the main loop, is it a concern or will it still run fine?
This is the service class in question:
public class BackgroundTasksService : BackgroundService
{
public IBackgroundTaskQueue TaskQueue { get; }
public BackgroundTasksService(IBackgroundTaskQueue taskQueue)
{
TaskQueue = taskQueue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await BackgroundProcessing(stoppingToken);
}
private async Task BackgroundProcessing(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem =
await TaskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
await base.StopAsync(stoppingToken);
}
}
This code was taken from
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-5.0&tabs=visual-studio#queued-background-tasks
The answer lies in what is happening if this code executes while there is nothing in the queue.
var workItem = await TaskQueue.DequeueAsync(stoppingToken);
The answer is that nothing is happening. This line of code doesn't execute over and over again until something is dequeued. That's why there's no need for a delay.
It also doesn't block until something is dequeued.
What happens is that the thread executing the method is freed to do something else. No thread is dedicated to the execution of DequeueAsync. (See this excellent post - There Is No Thread.)
When and if an item appears in the queue to be dequeued, then an available thread is assigned to resume execution of the method.
DequeueAsync only throws an exception if
the cancellation token is canceled
the queue's Complete method is called and the queue is empty, which means that you're no longer expecting anything to appear in the queue.

Why doesn't the .NET Generic Host stop when used with WinUI3?

I'm writing a WinUI3 (Project Reunion 0.5) application with .NET 5 and would like to use the .NET Generic Host. I'm using the default host with a custom IHostedService:
public App() {
_host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
services.AddHostedService<MyHostedService>();
}).Build();
InitializeComponent();
}
The hosted service performs some asynchronous operations in StopAsync. For demonstration purposes, let's say it delays for 1 second (this code still produces the issue):
public override async Task StopAsync(CancellationToken cancellationToken)
{
await Task.Delay(1000);
}
I start the host in OnLaunched:
protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
await _host.StartAsync();
m_window = new MainWindow();
m_window.Activate();
}
I let the default ConsoleLifetime implementation stop the host before the process exits.
The Task returned by my IHostedService.StopAsync implementation completes, but IHost.StopAsync never returns and the process hangs with this message in the output:
Microsoft.Hosting.Lifetime: Information: Application is shutting down...
Microsoft.Hosting.Lifetime: Information: Waiting for the host to be disposed. Ensure all 'IHost' instances are wrapped in 'using' blocks.
If I step through with the debugger, sometimes the IHost.StopAsync method will time out and an exception will be thrown. This never happens outside of the debugger. I have tried explicitly stopping and disposing the host when the MainWindow is closed, but it didn't make any difference.
I thought perhaps the DispatcherQueueSynchronizationContext was being shut down before the host could stop and tasks were not being serviced, but the DispatcherQueue.ShutdownStarting event is never fired.
Any other ideas?
I took #Dai's advice from the comments and investigated running WinUI on a separate thread and running the host on the main thread.
I created an IHostedService to manage the WinUI application:
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.System;
using Microsoft.UI.Xaml;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MyApp.Hosting
{
public class WinUIHostedService<TApplication> : IHostedService, IDisposable
where TApplication : Application, new()
{
private readonly IHostApplicationLifetime HostApplicationLifetime;
private readonly IServiceProvider ServiceProvider;
public WinUIHostedService(
IHostApplicationLifetime hostApplicationLifetime,
IServiceProvider serviceProvider)
{
HostApplicationLifetime = hostApplicationLifetime;
ServiceProvider = serviceProvider;
}
public void Dispose()
{
}
public Task StartAsync(CancellationToken cancellationToken)
{
var thread = new Thread(Main);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
private void Main()
{
WinRT.ComWrappersSupport.InitializeComWrappers();
Application.Start((p) => {
var context = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
new TApplication();
});
HostApplicationLifetime.StopApplication();
}
}
}
I defined DISABLE_XAML_GENERATED_MAIN in the build settings and added my own Main:
public class Program
{
public static void Main(string[] args)
{
Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.AddHostedService<WinUIHostedService<App>>();
})
.Build().Run();
}
}
Voila! The WinUI application still runs fine and the host stops cleanly when the main window closes, even when IHostedService.StopAsync runs asynchronous code.
Note that this code is just the first thing that worked. It could probably be improved and I don't fully understand the Generic Host lifetime semantics.

How to implement parallel long running background tasks in .NET Core 3?

I have .NET Core console application containing two independent tasks that should be running in parallel for the entire life-time of the application. I was thinking to use BackgroundService:
class BackgroundTaskOne : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
// do long running task for the entire life-time of application
while(true)
{
// do work one
}
}
catch (Exception e)
{
// log
}
}
return Task.CompletedTask;
}
}
class BackgroundTaskTwo : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
// do long running task for the entire life-time of application
while(true)
{
// do work two
}
}
catch (Exception e)
{
// log
}
}
return Task.CompletedTask;
}
}
And register them like this:
services.AddHostedService<BackgroundTaskOne>();
services.AddHostedService<BackgroundTaskTwo>();
But these are going to run in order. So I have two questions:
Is there a way to make these two run in parallel?
Are there any other alternatives to run two long-running background processes in .NET Core in parallel?
The docs of BackgroundService.ExecuteAsync say
The implementation should return a task that represents the lifetime of the long running operation(s) being performed.
Your implementation returns a completed task when the whole work is done. In fact you implemented it to run sync and not async and that is the reason for not running parallel.
Here is a sample implementation with some fake async work:
class BackgroundTaskOne : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
// do work one
await Task.Delay( 100 );
}
catch (Exception e)
{
// log
}
}
}
}
As documentation says:
https://github.com/dotnet/AspNetCore.Docs/blob/master/aspnetcore/fundamentals/host/hosted-services.md
No further services are started until ExecuteAsync becomes
asynchronous, such as by calling await.
As long as you do not have async clause mentioned at the ExecuteAsync method above, I suspect your method is synchronous at whole. This is why two services are called sequentially, not in parallel. Give them a break, introduce good amount of awaitable code.

Background tasks are being queued and not executed

I've implemented the BackgroundQueue as explained here, and as shown:
public ActionResult SomeAction()
{
backgroundQueue.QueueBackgroundWorkItem(async ct =>
{
//Do some work...
});
return Ok();
}
I registered the BackgroundQueue with Autofac as:
builder.RegisterType<BackgroundQueue>()
.As<IBackgroundQueue>()
.SingleInstance();
So far so good. I call my controller action and the task is added to the queue. And there it stays without being executed.
So how do I get the task to execute?
The BackgroundQueue implementation that you took from the documentation is only one part to the solution: The background queue will just keep track of the jobs that you want to be executed.
What you will also need is right below that in the docs: The QueuedHostedService. This is a background service that gets registered with the DI container and is started when the application starts. From then on, it will monitor your BackgroundQueue and work off jobs as they get queued.
A simplified example implementation of this background service, without logging or error handling, could look like this:
public class QueuedHostedService : BackgroundService
{
private readonly IBackgroundQueue _backgroundQueue;
public QueuedHostedService(IBackgroundQueue backgroundQueue)
{
_backgroundQueue = backgroundQueue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem = await _backgroundQueue.DequeueAsync(stoppingToken);
await workItem(stoppingToken);
}
}
}

Categories