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

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.

Related

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);
}
}
}

Simple async Task fails to continue in Wpf app, but works in Console app [duplicate]

I have the following four tests and the last one hangs when I run it. Why does this happen:
[Test]
public void CheckOnceResultTest()
{
Assert.IsTrue(CheckStatus().Result);
}
[Test]
public async void CheckOnceAwaitTest()
{
Assert.IsTrue(await CheckStatus());
}
[Test]
public async void CheckStatusTwiceAwaitTest()
{
Assert.IsTrue(await CheckStatus());
Assert.IsTrue(await CheckStatus());
}
[Test]
public async void CheckStatusTwiceResultTest()
{
Assert.IsTrue(CheckStatus().Result); // This hangs
Assert.IsTrue(await CheckStatus());
}
private async Task<bool> CheckStatus()
{
var restClient = new RestClient(#"https://api.test.nordnet.se/next/1");
Task<IRestResponse<DummyServiceStatus>> restResponse = restClient.ExecuteTaskAsync<DummyServiceStatus>(new RestRequest(Method.GET));
IRestResponse<DummyServiceStatus> response = await restResponse;
return response.Data.SystemRunning;
}
I use this extension method for restsharp RestClient:
public static class RestClientExt
{
public static Task<IRestResponse<T>> ExecuteTaskAsync<T>(this RestClient client, IRestRequest request) where T : new()
{
var tcs = new TaskCompletionSource<IRestResponse<T>>();
RestRequestAsyncHandle asyncHandle = client.ExecuteAsync<T>(request, tcs.SetResult);
return tcs.Task;
}
}
public class DummyServiceStatus
{
public string Message { get; set; }
public bool ValidVersion { get; set; }
public bool SystemRunning { get; set; }
public bool SkipPhrase { get; set; }
public long Timestamp { get; set; }
}
Why does the last test hang?
Acquiring a value via an async method:
var result = Task.Run(() => asyncGetValue()).Result;
Syncronously calling an async method
Task.Run( () => asyncMethod()).Wait();
No deadlock issues will occur due to the use of Task.Run.
You're running into the standard deadlock situation that I describe on my blog and in an MSDN article: the async method is attempting to schedule its continuation onto a thread that is being blocked by the call to Result.
In this case, your SynchronizationContext is the one used by NUnit to execute async void test methods. I would try using async Task test methods instead.
You can avoid deadlock adding ConfigureAwait(false) to this line:
IRestResponse<DummyServiceStatus> response = await restResponse;
=>
IRestResponse<DummyServiceStatus> response = await restResponse.ConfigureAwait(false);
I've described this pitfall in my blog post Pitfalls of async/await
You are blocking the UI by using Task.Result property.
In MSDN Documentation they have clearly mentioned that,
"The Result property is a blocking property. If you try to access it
before its task is finished, the thread that's currently active is
blocked until the task completes and the value is available. In most
cases, you should access the value by using Await or await instead of
accessing the property directly."
The best solution for this scenario would be to remove both await & async from methods & use only Task where you're returning result. It won't mess your execution sequence.
An addition to the answer given by #HermanSchoenfeld. Unfortunately the quote below is not true:
No deadlock issues will occur due to the use of Task.Run.
public String GetSqlConnString(RubrikkUser user, RubrikkDb db)
{
// deadlock if called from threadpool,
// works fine on UI thread, works fine from console main
return Task.Run(() =>
GetSqlConnStringAsync(user, db)).Result;
}
The execution is wrapped inside a Task.Run, this will schedule the task on the threadpool the block the calling thread. This is okay, as long as the calling thread is not a threadpool thread. If the calling thread is from the threadpool then the following disaster happens: A new task is queued to the end of the queue, and the threadpool thread which would eventually execute the Task is blocked until the Task is executed.
In library code there is no easy solution as you cannot assume under what context your code is called. The best solution is to only call async code from async code, blocking sync APIs from sync methods, don’t mix them.
Source:
https://medium.com/rubrikkgroup/understanding-async-avoiding-deadlocks-e41f8f2c6f5d
If you don't get any callbacks or the control hangs up, after calling the service/API async function, you have to configure Context to return a result on the same called context.
Use TestAsync().ConfigureAwait(continueOnCapturedContext: false);
You will be facing this issue only in web applications, but not in static void main.

Async task not returning value, freezing after compilation. Wpf [duplicate]

I have the following four tests and the last one hangs when I run it. Why does this happen:
[Test]
public void CheckOnceResultTest()
{
Assert.IsTrue(CheckStatus().Result);
}
[Test]
public async void CheckOnceAwaitTest()
{
Assert.IsTrue(await CheckStatus());
}
[Test]
public async void CheckStatusTwiceAwaitTest()
{
Assert.IsTrue(await CheckStatus());
Assert.IsTrue(await CheckStatus());
}
[Test]
public async void CheckStatusTwiceResultTest()
{
Assert.IsTrue(CheckStatus().Result); // This hangs
Assert.IsTrue(await CheckStatus());
}
private async Task<bool> CheckStatus()
{
var restClient = new RestClient(#"https://api.test.nordnet.se/next/1");
Task<IRestResponse<DummyServiceStatus>> restResponse = restClient.ExecuteTaskAsync<DummyServiceStatus>(new RestRequest(Method.GET));
IRestResponse<DummyServiceStatus> response = await restResponse;
return response.Data.SystemRunning;
}
I use this extension method for restsharp RestClient:
public static class RestClientExt
{
public static Task<IRestResponse<T>> ExecuteTaskAsync<T>(this RestClient client, IRestRequest request) where T : new()
{
var tcs = new TaskCompletionSource<IRestResponse<T>>();
RestRequestAsyncHandle asyncHandle = client.ExecuteAsync<T>(request, tcs.SetResult);
return tcs.Task;
}
}
public class DummyServiceStatus
{
public string Message { get; set; }
public bool ValidVersion { get; set; }
public bool SystemRunning { get; set; }
public bool SkipPhrase { get; set; }
public long Timestamp { get; set; }
}
Why does the last test hang?
Acquiring a value via an async method:
var result = Task.Run(() => asyncGetValue()).Result;
Syncronously calling an async method
Task.Run( () => asyncMethod()).Wait();
No deadlock issues will occur due to the use of Task.Run.
You're running into the standard deadlock situation that I describe on my blog and in an MSDN article: the async method is attempting to schedule its continuation onto a thread that is being blocked by the call to Result.
In this case, your SynchronizationContext is the one used by NUnit to execute async void test methods. I would try using async Task test methods instead.
You can avoid deadlock adding ConfigureAwait(false) to this line:
IRestResponse<DummyServiceStatus> response = await restResponse;
=>
IRestResponse<DummyServiceStatus> response = await restResponse.ConfigureAwait(false);
I've described this pitfall in my blog post Pitfalls of async/await
You are blocking the UI by using Task.Result property.
In MSDN Documentation they have clearly mentioned that,
"The Result property is a blocking property. If you try to access it
before its task is finished, the thread that's currently active is
blocked until the task completes and the value is available. In most
cases, you should access the value by using Await or await instead of
accessing the property directly."
The best solution for this scenario would be to remove both await & async from methods & use only Task where you're returning result. It won't mess your execution sequence.
An addition to the answer given by #HermanSchoenfeld. Unfortunately the quote below is not true:
No deadlock issues will occur due to the use of Task.Run.
public String GetSqlConnString(RubrikkUser user, RubrikkDb db)
{
// deadlock if called from threadpool,
// works fine on UI thread, works fine from console main
return Task.Run(() =>
GetSqlConnStringAsync(user, db)).Result;
}
The execution is wrapped inside a Task.Run, this will schedule the task on the threadpool the block the calling thread. This is okay, as long as the calling thread is not a threadpool thread. If the calling thread is from the threadpool then the following disaster happens: A new task is queued to the end of the queue, and the threadpool thread which would eventually execute the Task is blocked until the Task is executed.
In library code there is no easy solution as you cannot assume under what context your code is called. The best solution is to only call async code from async code, blocking sync APIs from sync methods, don’t mix them.
Source:
https://medium.com/rubrikkgroup/understanding-async-avoiding-deadlocks-e41f8f2c6f5d
If you don't get any callbacks or the control hangs up, after calling the service/API async function, you have to configure Context to return a result on the same called context.
Use TestAsync().ConfigureAwait(continueOnCapturedContext: false);
You will be facing this issue only in web applications, but not in static void main.

async await async all the way

I'm new to c# async await mechanism. I read some articles about async all the way (http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html). I have an example below, could you please let me know if the first Initialize method will cause dead lock and why? Thank you in advance.
public class Test {
public async Task DoSomeWorkAsync() {
await DoSomeWork1Async();
}
// This method is mixed with Wait and async await, will this cause lead lock?
public void Initialize() {
Task.Run(() => DoSomeWorkAsync()).Wait();
}
// This method is following async all the way
public async Task InitializeAsync() {
await DoSomeWorkAsync();
}
}
// Update: Here is the context where two Initialize methods are called
public class TestForm : Form {
// Load Form UI
public async void OnLoad() {
var test = new Test();
test.Initialize();
await test.InitializeAsync();
}
}
No, this will not deadlock because you're blocking on a task that's being executed by a ThreadPool thread with no SynchronizationContext. Since it isn't running on the UI thread there's nothing stopping that task from completing and so there's no deadlock.
If this was your code, it will have deadlocked:
public void Initialize()
{
DoSomeWorkAsync().Wait();
}
This is still not a good reason to block though, you should use async-await all they way up.

Categories