Fire and Forget using Async over Sync - c#

We are using Umbraco 8 which have IComponent which allows us to hook events(which does not have async support). We have enabled hook like,
public class MyHookComponent : IComponent
{
private static readonly HttpClient _httpClient = new HttpClient();
private readonly ILogger _logger;
static MyHookComponent()
{
_httpClient.BaseAddress = new SomeBaseUrl;
}
public MyHookComponent(ILogger logger)
{
_logger = logger;
}
public void Initialize()
{
ContentService.Published += OnNodePublished;
}
private void OnNodePublished(IContentService contentService, ContentPublishedEventArgs e)
{
// Do some work
Task.Run(() => SomeAsyncWork(url));
}
private async Task SomeAsyncWork(string url)
{
try
{
await _httpClient.PostAsync(url, null);
}
catch (Exception ex)
{
_logger.Error(typeof(MyHookComponent), ex);
}
}
Fire and forget style is from https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#async-void
Does the above code lead to deadlock or thread starvation as I we got some issue(very slow response after this change suddenly) on staging which (but no dump for investigation).
We have System.Web.Http, Version=5.2.7.0

Related

Worker Service dynamically changing execution interval

I have a worker service with the base class for all workers, which accepts the IOptionsMonitor constructor parameter. This monitor contains options object instance with the execution interval value. The question is how to dynamically change the interval, even when the await Task.Delay(Interval); was called? I mean if the Interval value is set to one day, and after the Task.Delay method being called it changes, for example, to one hour - I still need to wait one day and only on the next call the delay would be updated. How I can cancel the current delay and start a new one if the property Interval value was updated?
Thanks.
Please see the code attached below:
public abstract class WorkerBase<TWorker, TWorkerOptions> : BackgroundService
where TWorker : WorkerBase<TWorker, TWorkerOptions>
where TWorkerOptions : IHaveIntervalProperty
{
protected WorkerBase(IServiceProvider serviceProvider, ILogger<TWorker> logger, IOptionsMonitor<TWorkerOptions> options)
{
_logger = logger;
_serviceProvider = serviceProvider;
_workerName = typeof(TWorker).Name;
Interval = options.CurrentValue.Interval;
options.OnChange(UpdateOptions);
}
public TimeSpan Interval { get; private set; }
public virtual void UpdateOptions(TWorkerOptions options)
=> Interval = options.Interval;
public abstract Task DoWork(IServiceProvider provider);
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation(Logs.InformationWorkerRunning, _workerName, DateTime.UtcNow);
try
{
using var scope = _serviceProvider.CreateScope();
await DoWork(scope.ServiceProvider);
}
catch (Exception e)
{
_logger.LogCritical(e, e.Message);
}
finally
{
await Task.Delay(Interval, stoppingToken);
}
}
}
}
Okay, so based on #Panagiotis Kanavos comment I came up with the following code:
public abstract class RepeatableWorker<TWorker, TOptions> : IHostedService, IDisposable
where TWorker : RepeatableWorker<TWorker, TOptions>
where TOptions : IHaveIntervalProperty
{
#region Fields
private readonly IServiceProvider _serviceProvider;
private protected readonly ILogger<TWorker> _logger;
private readonly string _workerName;
private Timer? _executionTimer;
private TimeSpan _interval;
#endregion
#region Constructors
protected RepeatableWorker(IServiceProvider serviceProvider,
ILogger<TWorker> logger,
IOptionsMonitor<TOptions> options)
{
_serviceProvider = serviceProvider;
_logger = logger;
_workerName = typeof(TWorker).Name;
_interval = options.CurrentValue.Interval;
options.OnChange(UpdateOptions);
}
#endregion
#region Properties
public TimeSpan Interval
{
get => _interval;
private set
{
if (value != _interval)
{
_executionTimer?.Change(TimeSpan.Zero, value);
_interval = value;
}
}
}
#endregion
#region Public methods
public virtual void UpdateOptions(TOptions options)
=> Interval = options.Interval;
public abstract void DoWork(IServiceProvider serviceProvider);
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation(Logs.InformationWorkerStarting, _workerName, DateTime.UtcNow);
_executionTimer = new(DoWorkInternal, null, TimeSpan.Zero, Interval);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation(Logs.InformationWorkerStopping, _workerName, DateTime.UtcNow);
_executionTimer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
GC.SuppressFinalize(this);
_executionTimer?.Dispose();
}
#endregion
#region Private methods
private void DoWorkInternal(object? state)
{
try
{
_logger.LogInformation("Worker {0} running at {1}.", _workerName, DateTime.UtcNow);
using var scope = _serviceProvider.CreateScope();
DoWork(scope.ServiceProvider);
}
catch (Exception e)
{
_logger.LogCritical(e, e.Message);
}
}
#endregion
}
And the IHaveIntervalProperty interface:
public interface IHaveIntervalProperty
{
TimeSpan Interval { get; set; }
}
Just in case someone will need such a solution.

Integration between SignalR and WebAPI

I have a WebAPI server with integrated SignalR Hubs.
The problem it is in the integration between both components and efficiently call the clients interested on a given item that was updated through REST with the least overhead possible on the Controller side.
I have read about Background tasks with hosted services in ASP.NET Core, or Publish Subscriber Patterns but they don't seem the right fit for this problem.
From the documentation examples, the background tasks seem to atempt to preserve order which is not required, in fact, it is desired to allow multiple requests to be handled concurrently, as efficiently as possible.
With this in mind, I created this third component called MappingComponent that is being called through a new Task.
It is important to design the Controller in a way that he spends the least amount of work "raising the events" possible. Exceptions should be (i believe) handled within the MappingComponent.
What would be a better approach/design pattern that the following implementation, to avoid using Task.Run?
ApiController
[Route("api/[controller]")]
[ApiController]
public class ItemController : ControllerBase
{
private readonly MappingComponent mappingComponent;
private readonly IDataContext dataContext;
[HttpPost]
public async Task<ActionResult<Item>> PostItem(ItemDTO itemDTO)
{
await dataContext.Items.AddAsync(item);
(...)
_ = Task.Run(async () =>
{
try
{
await mappingComponent.NotifyOnItemAdd(item);
}
catch (Exception e)
{
Console.WriteLine(e);
}
});
return CreatedAtAction("Get", new { id = item.Id }, item);
}
[HttpDelete("{id}", Name = "Delete")]
public async Task<IActionResult> Delete(int id)
{
var item = await dataContext.Items.FindAsync(id);
(...)
_ = Task.Run(async () =>
{
try
{
await mappingComponent.NotifyOnItemDelete(item);
}
catch (Exception e)
{
Console.WriteLine(e);
}
});
return NoContent();
}
[HttpPatch("{id}", Name = "Patch")]
public async Task<IActionResult> Patch(int id,
[FromBody] JsonPatchDocument<Item> itemToPatch)
{
var item = await dataContext.Items.FindAsync(id);
(...)
_ = Task.Run(async () =>
{
try
{
await mappingComponent.NotifyOnItemEdit(item);
}
catch (Exception e)
{
Console.WriteLine(e);
}
});
return StatusCode(StatusCodes.Status202Accepted);
}
}
SignalR Hub
public class BroadcastHub : Hub<IHubClient>
{
private readonly MappingComponent mappingComponent;
public BroadcastHub(MappingComponent mappingComponent)
{
this.mappingComponent = mappingComponent;
}
public override Task OnConnectedAsync()
{
mappingComponent.OnConnected(Context.User.Identity.Name, Context.ConnectionId));
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync()
{
mappingComponent.OnDisconnected(Context.User.Identity.Name, Context.ConnectionId));
return base.OnDisconnectedAsync();
}
public void Subscribe(string itemQuery)
{
mappingComponent.SubscribeConnection(Context.User.Identity.Name, Context.ConnectionId, itemQuery));
}
public void Unsubscribe()
{
mappingComponent.UnsubscribeConnection(Context.ConnectionId));
}
}
"MappingComponent" being registered as singleton on startup
public class MappingComponent
{
private readonly IServiceScopeFactory scopeFactory;
private readonly IHubContext<BroadcastHub, IHubClient> _hubContext;
private static readonly ConcurrentDictionary<string, User> Users = new(StringComparer.InvariantCultureIgnoreCase);
private static readonly ConcurrentDictionary<int, List<string>> ItemConnection = new();
private static readonly ConcurrentDictionary<string, List<int>> ConnectionItem = new();
public MappingComponent(IServiceScopeFactory scopeFactory, IHubContext<BroadcastHub, IHubClient> hubContext)
{
//this.dataContext = dataContext;
this._hubContext = hubContext;
this.scopeFactory = scopeFactory;
}
internal void OnConnected(string userName, string connectionId){(...)}
internal void OnDisconnected(string userName, string connectionId){(...)}
internal void SubscribeConnection(string userName, string connectionId, string query){(...)}
internal void UnsubscribeConnection(string connectionId){(...)}
internal async Task NotifyOnItemAdd(Item item)
{
List<string> interestedConnections = new();
(...)
//Example containing locks
lock()
{
//There is a need to acess EF database
using (var scope = scopeFactory.CreateScope())
{
var dataContext = scope.ServiceProvider.GetRequiredService<IDataContext>()
await dataContext.Items.(...)
interestedConnections = ...
}
}
await _hubContext.Clients.Clients(interestedConnections).BroadcastItem(item);
}
internal async Task NotifyOnItemEdit(Item item)
{
List<string> interestedConnections = new();
(...)
await _hubContext.Clients.Clients(interestedConnections).BroadcastItem(item);
}
internal async Task NotifyOnItemDelete(Item item)
{
List<string> interestedConnections = new();
(...)
await _hubContext.Clients.Clients(interestedConnections).BroadcastAllItems();
}
}

Unit testing an async method that uses System.Threading.Timer

In .NET Core, background tasks are implemented as IHostedService. This is my hosted service:
public interface IMyService {
void DoStuff();
}
public class MyHostedService : IHostedService, IDisposable
{
private const int frequency;
private readonly IMyService myService;
private Timer timer;
public MyHostedService(IMyService myService, Setting s)
{
this.myService = myService;
frequency = s.Frequency;
}
public void Dispose()
{
this.timer?.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
this.timer = new Timer(this.DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(this.frequency));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
this.timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
private void DoWork(object state)
{
try
{
this.myService.DoStuff();
}
catch (Exception e)
{
// log
}
}
}
I am trying to unit test this class, and all I want is to make sure DoStuff gets called when StartAsync method is called. This is my unit test:
[TestFixture]
public class MyHostedServiceTests
{
[SetUp]
public void SetUp()
{
this.myService = new Mock<IMyService>();
this.hostedService = new MyHostedService(this.myService.Object, new Setting { Frequency = 60 });
}
private Mock<ImyService> myService;
private MyHostedService hostedService;
[Test]
public void StartAsync_Success()
{
this.hostedService.StartAsync(CancellationToken.None);
this.myService.Verify(x => x.DoStuff(), Times.Once);
}
}
Why is this failing?
It is failing because the async code is executing on a separate thread to the code that is verifying the expected behavior. That and the fact the the verifying code in invoked before the timer has had time to be invoked.
When testing an async method the test in most cases should also be async.
In this case you also need to let some time pass to allow the timer to invoke.
use Task.Delay to give the timer enough time to perform its function.
For example
[TestFixture]
public class MyHostedServiceTests {
[SetUp]
public void SetUp() {
this.myService = new Mock<IMyService>();
this.setting = new Setting { Frequency = 2 };
this.hostedService = new MyHostedService(this.myService.Object, setting);
}
private Mock<ImyService> myService;
private MyHostedService hostedService;
private Setting setting;
[Test]
public async Task StartAsync_Success() {
//Act
await this.hostedService.StartAsync(CancellationToken.None);
await Task.Delay(TimeSpan.FromSeconds(1));
await this.hostedService.StopAsync(CancellationToken.None);
//Assert
this.myService.Verify(x => x.DoStuff(), Times.Once);
}
}
Above example uses a shorter frequency to test the expected behavior

Background task of writing to the database by timer

How to write to the database on a timer in the background. For example, check mail and add new letters to the database. In the example, I simplified the code just before writing to the database.
The class names from the example in Microsoft.
The recording class itself:
namespace EmailNews.Services
{
internal interface IScopedProcessingService
{
void DoWork();
}
internal class ScopedProcessingService : IScopedProcessingService
{
private readonly ApplicationDbContext _context;
public ScopedProcessingService(ApplicationDbContext context)
{
_context = context;
}
public void DoWork()
{
Mail mail = new Mail();
mail.Date = DateTime.Now;
mail.Note = "lala";
mail.Tema = "lala";
mail.Email = "lala";
_context.Add(mail);
_context.SaveChangesAsync();
}
}
}
Timer class:
namespace EmailNews.Services
{
#region snippet1
internal class TimedHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
public TimedHostedService(IServiceProvider services, ILogger<TimedHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromMinutes(1));
return Task.CompletedTask;
}
private void DoWork(object state)
{
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
scopedProcessingService.DoWork();
}
}
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();
}
}
#endregion
}
Startup:
services.AddHostedService<TimedHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
It seems everything is done as in the example, but nothing is added to the database, which is not so?
This is a rather interesting question, that boils down to "How do you correctly handle an async timer callback?"
The immediate problem is that SaveChangesAsync isn't getting awaited. The DbContext almost certainly gets disposed before SaveChangesAsync has a chance to run. To await it, DoWork must become an async Task method (never async void) :
internal interface IScheduledTask
{
Task DoWorkAsync();
}
internal class MailTask : IScheduledTask
{
private readonly ApplicationDbContext _context;
public MailTask(ApplicationDbContext context)
{
_context = context;
}
public async Task DoWorkAsync()
{
var mail = new Mail
{ Date = DateTime.Now,
Note = "lala",
Tema = "lala",
Email = "lala" };
_context.Add(mail);
await _context.SaveChangesAsync();
}
}
The problem now is how to call DoWorkAsync from the timer callback. If we just call it without awaiting, we'll get the same problem we had in the first place. A timer callback can't handle methods that return Task. We can't make it async void either, because this would result in the same problem - the method will return before any async operation has a chance to finish.
David Fowler explains how to properly handle asynchronous timer callbacks in the Timer Callbacks section of his Async Guidance
article :
private readonly Timer _timer;
private readonly HttpClient _client;
public Pinger(HttpClient client)
{
_client = new HttpClient();
_timer = new Timer(Heartbeat, null, 1000, 1000);
}
public void Heartbeat(object state)
{
// Discard the result
_ = DoAsyncPing();
}
private async Task DoAsyncPing()
{
await _client.GetAsync("http://mybackend/api/ping");
}
The actual method should be async Task but the returned task only has to be assigned, not awaited, in order for it to work properly.
Applying this to the question leads to something like this :
public Task StartAsync(CancellationToken cancellationToken)
{
...
_timer = new Timer(HeartBeat, null, TimeSpan.Zero,
TimeSpan.FromMinutes(1));
return Task.CompletedTask;
}
private void Heartbeat(object state)
{
_ = DoWorkAsync();
}
private async Task DoWorkAsync()
{
using (var scope = Services.CreateScope())
{
var schedTask = scope.ServiceProvider
.GetRequiredService<IScheduledTask>();
await schedTask.DoWorkAsync();
}
}
David Fowler explains why async void is ALWAY BAD in ASP.NET Core - it's not only that async actions won't be awaited, exceptions will crash the application.
He also explains why we can't use Timer(async state=>DoWorkAsync(state)) - that's an async void delegate.

.Net Core Queue Background Tasks

Slender answered my original question about what happens to fire and forget, after the HTTP Response is sent, but Now I'm left with the question how to properly queue background tasks
EDIT
As we all know Async void is generally bad, except for in the case when it comes to event handlers, I would like to execute some background logic without have to have the client wait. My original Idea was to use Fire and Forget
Say I have an event:
public event EventHandler LongRunningTask;
And then someone subscribes a fire and forget task:
LongRunningTask += async(s, e) => { await LongNetworkOperation;};
the web api method is call:
[HttpGet]
public async IActionResult GetTask()
{
LongRunningTask?.Invoke(this, EventArgs.Empty);
return Ok();
}
But If I do this my long running task isn't guaranteed to finish, How can I handle running background task without affect the time the time it take to make my request (e.g I don't want to wait for the task to finish first)?
.NET Core 2.1 has an IHostedService, which will safely run tasks in the background. I've found an example in the documentation for QueuedHostedService which I've modified to use the BackgroundService.
public class QueuedHostedService : BackgroundService
{
private Task _backgroundTask;
private readonly ILogger _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue, ILoggerFactory loggerFactory)
{
TaskQueue = taskQueue;
_logger = loggerFactory.CreateLogger<QueuedHostedService>();
}
public IBackgroundTaskQueue TaskQueue { get; }
protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (false == stoppingToken.IsCancellationRequested)
{
var workItem = await TaskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
this._logger.LogError(ex, $"Error occurred executing {nameof(workItem)}.");
}
}
}
}
public interface IBackgroundTaskQueue
{
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private ConcurrentQueue<Func<CancellationToken, Task>> _workItems =
new ConcurrentQueue<Func<CancellationToken, Task>>();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
public async Task<Func<CancellationToken, Task>> DequeueAsync( CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem;
}
}
Now we can safely queue up tasks in the background without affecting the time it takes to respond to a request.
Just wanted to add some additional notes to #johnny5 answer. Right now you can use https://devblogs.microsoft.com/dotnet/an-introduction-to-system-threading-channels/ instead of ConcurrentQueue with Semaphore.
The code will be something like this:
public class HostedService: BackgroundService
{
private readonly ILogger _logger;
private readonly ChannelReader<Stream> _channel;
public HostedService(
ILogger logger,
ChannelReader<Stream> channel)
{
_logger = logger;
_channel = channel;
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
await foreach (var item in _channel.ReadAllAsync(cancellationToken))
{
try
{
// do your work with data
}
catch (Exception e)
{
_logger.Error(e, "An unhandled exception occured");
}
}
}
}
[ApiController]
[Route("api/data/upload")]
public class UploadController : ControllerBase
{
private readonly ChannelWriter<Stream> _channel;
public UploadController (
ChannelWriter<Stream> channel)
{
_channel = channel;
}
public async Task<IActionResult> Upload([FromForm] FileInfo fileInfo)
{
var ms = new MemoryStream();
await fileInfo.FormFile.CopyToAsync(ms);
await _channel.WriteAsync(ms);
return Ok();
}
}

Categories