Mass Transit RabbitMQ Consumer fails with long task - c#

Error Message: MassTransit.ConnectionException: The connection is stopping and cannot be used: rabbit-host
We have a long-running consumer using MassTransit with RabbitMQ.
We are failing in the consumer when trying to publish our result onto a different queue after running for 20+ minutes.
We are assuming that the connection is timing out before we complete our work.
We see there is an option to use a JobConsumer for long running tasks, but were wondering if there is a way to extend our timeout on the regular Consumer when working with RabbitMQ?
We saw the MaxAutoRenewDuration option when working with Azure on this question: Masstransit - long running process and imediate response and were looking for something similar for RabbitMQ.
Is there a specific default timeout time that the connection to the rabbit host lasts?
Thank you for any help, and I can provide more details if that'd be helpful.
// Consumer Class
using System;
using System.Threading.Tasks;
using MassTransit;
namespace ExampleNameSpace
{
public class ExampleConsumer : IConsumer<ExampleMessage>
{
private readonly BusinessLogicProcess _businessLogicProcess;
public ExampleConsumer(BusinessLogicProcess businessLogicProcess)
{
_businessLogicProcess = businessLogicProcess;
}
public async Task Consume(ConsumeContext<ExampleMessage> context)
{
try
{
// Do our business logic (takes 20+ minutes)
var businessLogicResult = await _businessLogicProcess.DoBusinessWorkAsync();
var resultMessage = new ResultMessage { ResultValue = businessLogicResult };
// Publish the result of our work
// Get the following error when we publish after a long running piece of work
// MassTransit.ConnectionException: The connection is stopping and cannot be used: rabbitmqs://our-rabbit-host/vhost-name
await context.Publish<ResultMessage>(resultMessage);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
public class BusinessLogicProcess {
public async Task<int> DoBusinessWorkAsync()
{
// Business Logic Here
// Takes 20+ minutes
return 0;
}
}
public class ExampleMessage { }
public class ResultMessage {
public int ResultValue { get; set; }
}
}
// Service Class - Connect to RabbitMQ
using MassTransit;
using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ExampleNameSpace
{
public class ExampleService : BackgroundService
{
private readonly BusinessLogicProcess _businessLogicProcess;
private IBusControl _bus;
public ExampleService(BusinessLogicProcess process)
{
_businessLogicProcess = process;
}
public override async Task StartAsync(CancellationToken cancellationToken)
{
_bus = Bus.Factory.CreateUsingRabbitMq(
config =>
{
config.Host(new Uri("rabbitmqs://our-rabbit-host/vhost-name"), hostConfig =>
{
hostConfig.Username("guest");
hostConfig.Password("guest");
});
config.ReceiveEndpoint("exampleendpoint",
endpointConfigurator =>
{
endpointConfigurator.Consumer(() => new ExampleConsumer(_businessLogicProcess),
config => config.UseConcurrentMessageLimit(1));
});
});
await _bus.StartAsync(cancellationToken);
await base.StopAsync(cancellationToken);
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
return Task.CompletedTask;
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
await _bus.StopAsync(cancellationToken);
await base.StopAsync(cancellationToken);
}
}
}

Related

C# dependency injection has duplicate singletons

I'm working on a Xamarin.Forms app and for some reason there are singletons that are created multiple times. This does not happen every time however and it seems to be at random. My dependency injection setup happens in the App.xaml.cs
using System;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
using Microsoft.Extensions.DependencyInjection;
using Peripass.Mobile.Framework;
using Peripass.Mobile.Framework.DependencyInterfaces;
using Peripass.YardAssetManagementApp.Data;
using Peripass.YardAssetManagementApp.Data.FileSystem;
using Peripass.YardAssetManagementApp.Data.LocalDataServices;
using Peripass.YardAssetManagementApp.Data.Queues;
using Peripass.YardAssetManagementApp.Data.RemoteDataServices;
using Peripass.YardAssetManagementApp.Data.Syncing;
using Peripass.YardAssetManagementApp.Data.Syncing.QueueItemSyncActions;
using Peripass.YardAssetManagementApp.Device;
using Peripass.YardAssetManagementApp.MVVM;
using Peripass.YardAssetManagementApp.MVVM.Pages;
using Peripass.YardAssetManagementApp.PushNotifications;
using Peripass.YardAssetManagementApp.Services;
using Peripass.YardAssetManagementApp.StepsWizard;
using Peripass.YardAssetManagementApp.UI.Views;
using Peripass.YardAssetManagementApp.UserAuthentication;
using Peripass.YardAssetManagementApp.ViewModels;
using Xamarin.Forms;
using Application = Xamarin.Forms.Application;
namespace Peripass.YardAssetManagementApp {
public partial class App : Application, IApp {
private string _startedByAndroidNotificationMessage;
public App() {
ConfigureServices();
InitializeComponent();
AppCenter.Start($"android={Configuration.Configuration.ApplicationInsightsKeyAndroid};", typeof(Analytics), typeof(Crashes));
MainPage = ServiceProvider.GetRequiredService<MainPage>();
}
public static IServiceCollection Services { get; } = new ServiceCollection();
public IServiceProvider ServiceProvider { get; set; }
private void ConfigureServices() {
Services.AddSingleton<ILifecycleHooksService, LifecycleHooksService>();
Services.AddTransient<INetworkInformationProvider, NetworkInformationProvider>();
RegisterLocalDataServices();
RegisterRemoteDataServices();
RegisterSyncLogic();
RegisterServices();
RegisterViews();
Services.AddSingleton<IUserManager, UserManager>();
Services.AddSingleton<HttpClientProvider>();
Services.AddSingleton<ILogger, Logger>();
Services.AddSingleton<INavigationService, NavigationService>();
Services.AddSingleton<StepsWizardManager>();
Services.AddSingleton<MainPage>();
Services.AddSingleton(DependencyService.Get<ILocalFileSystem>());
Services.AddSingleton<IRestServiceHelper, RestServiceHelper>();
Services.AddSingleton<IPushNotificationsService, PushNotificationsService>();
ServiceProvider = Services.BuildServiceProvider();
}
public void RegisterServices() {
Services.AddTransient<ITaskService, TaskService>();
Services.AddTransient<ILocationService, LocationService>();
Services.AddTransient<ILocalizationService, LocalizationService>();
Services.AddTransient<IDiagnosticsService, DiagnosticsService>();
}
public void RegisterLocalDataServices() {
Services.AddSingleton<ILocalTaskDataService, LocalTaskDataService>();
Services.AddSingleton<ILocalLocationDataService, LocalLocationDataService>();
Services.AddSingleton<ILocalUserDataService, LocalUserDataService>();
Services.AddSingleton<ILocalPushNotificationDataService, LocalPushNotificationDataService>();
Services.AddSingleton<ILocalPictureDataService, LocalPictureDataService>();
Services.AddSingleton<ISafeFileSystem, SafeFileSystem>();
Services.AddSingleton<ILocalLocalizationDataService, LocalLocalizationDataService>();
}
public void RegisterRemoteDataServices() {
Services.AddSingleton<IRemoteTaskDataService, RemoteTaskDataService>();
Services.AddSingleton<IRemoteLocationDataService, RemoteLocationDataService>();
Services.AddSingleton<IRemoteUserDataService, RemoteUserDataService>();
Services.AddSingleton<IRemoteFileDataService, RemoteFileDataService>();
Services.AddSingleton<IRemoteLocalizationDataService, RemoteLocalizationDataService>();
}
public void RegisterSyncLogic() {
Services.AddSingleton<IToMobileTasksSyncer, ToMobileTasksSyncer>();
Services.AddSingleton<IQueue, IncomingHighPriorityQueue>();
Services.AddSingleton<IQueue, IncomingLowPriorityQueue>();
Services.AddSingleton<IQueue, OutgoingHighPriorityQueue>();
Services.AddSingleton<IQueue, OutgoingLowPriorityQueue>();
Services.AddSingleton<IQueue, PictureSyncLowPriorityQueue>();
Services.AddSingleton<IQueue, PictureSyncHighPriorityQueue>();
Services.AddSingleton<IQueueOrchestrator, IncomingQueueOrchestrator>();
Services.AddSingleton<IQueueOrchestrator, OutgoingQueueOrchestrator>();
Services.AddSingleton<IQueueOrchestrator, PictureQueueOrchestrator>();
Services.AddSingleton<IQueueProcessor, IncomingQueueProcessor>();
Services.AddSingleton<IQueueProcessor, OutgoingQueueProcessor>();
Services.AddSingleton<IQueueProcessor, PictureQueueProcessor>();
Services.AddSingleton<IQueueItemSyncAction, FetchTaskDetailSyncAction>();
Services.AddSingleton<IQueueItemSyncAction, OutgoingStepValueUpdateSyncAction>();
Services.AddSingleton<IQueueItemSyncAction, OutgoingTaskStatusChangedSyncAction>();
Services.AddSingleton<IQueueItemSyncAction, SyncPictureAction>();
Services.AddSingleton<IFileIntegrityHelper, FileIntegrityHelper>();
}
public void RegisterViews() {
Services.AddTransient<LoginViewContent>();
Services.AddSingleton<LoginViewModel>();
Services.AddTransient<UserInfoViewContent>();
Services.AddSingleton<UserInfoViewModel>();
Services.AddTransient<MainTaskListViewContent>();
Services.AddSingleton<MainTaskListViewModel>();
Services.AddTransient<TaskDetailViewContent>();
Services.AddSingleton<TaskDetailViewModel>();
Services.AddTransient<IntegerFieldTaskStepViewContent>();
Services.AddSingleton<IntegerFieldTaskStepViewModel>();
Services.AddTransient<TextAreaTaskStepViewContent>();
Services.AddSingleton<TextAreaTaskStepViewModel>();
Services.AddTransient<DropDownTaskStepViewContent>();
Services.AddSingleton<DropDownTaskStepViewModel>();
Services.AddTransient<MoveToLocationSummaryViewContent>();
Services.AddSingleton<MoveToLocationSummaryViewModel>();
Services.AddTransient<TakePictureStepViewContent>();
Services.AddSingleton<TakePictureStepViewModel>();
Services.AddTransient<MoveToLocationStepViewContent>();
Services.AddSingleton<MoveToLocationStepViewModel>();
Services.AddTransient<TaskCompletedViewContent>();
Services.AddSingleton<TaskCompletedViewModel>();
Services.AddTransient<LoggedOutViewContent>();
Services.AddSingleton<LoggedOutViewModel>();
Services.AddTransient<TextFieldTaskStepViewContent>();
Services.AddSingleton<TextFieldTaskStepViewModel>();
Services.AddTransient<DecimalFieldTaskStepViewContent>();
Services.AddSingleton<DecimalFieldTaskStepViewModel>();
Services.AddTransient<GenericExceptionPageViewContent>();
Services.AddSingleton<GenericExceptionPageViewModel>();
Services.AddTransient<CleanupBeforeLogoutPageViewContent>();
Services.AddSingleton<CleanupBeforeLogoutPageViewModel>();
Services.AddTransient<SelectLanguageViewContent>();
Services.AddSingleton<SelectLanguageViewModel>();
}
protected override async void OnStart() {
await ServiceProvider.GetRequiredService<ILifecycleHooksService>().OnStart();
}
protected override void OnSleep() { }
protected override async void OnResume() {
await ServiceProvider.GetRequiredService<ILifecycleHooksService>().OnResume();
}
public void StartedByTapOnAndroidNotification(string message) {
_startedByAndroidNotificationMessage = message;
}
}
}
The singletons that are created multiple times are these ones. It could be that other singletons are also created muiltiple times but these I'm sure of.
Services.AddSingleton<IQueueOrchestrator, IncomingQueueOrchestrator>();
Services.AddSingleton<IQueueOrchestrator, OutgoingQueueOrchestrator>();
Services.AddSingleton<IQueueOrchestrator, PictureQueueOrchestrator>();
Services.AddSingleton<IQueueProcessor, IncomingQueueProcessor>();
Services.AddSingleton<IQueueProcessor, OutgoingQueueProcessor>();
Services.AddSingleton<IQueueProcessor, PictureQueueProcessor>();
I see that the OnStart method is sometimes called multiple times creating multiple instances of these singletons when resolving the dependencies of the ILifecycleHooksService. This is the code in that LifeCycleHooksService:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Peripass.YardAssetManagementApp.Data;
using Peripass.YardAssetManagementApp.Data.LocalDataServices;
using Peripass.YardAssetManagementApp.Data.Syncing;
using Peripass.YardAssetManagementApp.MVVM;
using Peripass.YardAssetManagementApp.PushNotifications;
using Peripass.YardAssetManagementApp.ViewModels;
namespace Peripass.YardAssetManagementApp {
public interface ILifecycleHooksService {
Task OnStart();
Task OnResume();
}
public class LifecycleHooksService : ILifecycleHooksService {
private readonly INavigationService _navigationService;
private readonly ILocalUserDataService _localUserDataService;
private readonly ILocalTaskDataService _localTaskDataService;
private readonly ILocalLocationDataService _localLocationDataService;
private readonly ILocalPushNotificationDataService _localPushNotificationDataService;
private readonly ILocalPictureDataService _localPictureDataService;
private readonly IToMobileTasksSyncer _toMobileTasksSyncer;
private readonly IEnumerable<IQueueProcessor> _queueProcessors;
private readonly IPushNotificationsService _pushNotificationsService;
private readonly ILocalLocalizationDataService _localLocalizationDataService;
public LifecycleHooksService(INavigationService navigationService,
ILocalUserDataService localUserDataService,
IToMobileTasksSyncer toMobileTasksSyncer,
IEnumerable<IQueueProcessor> queueProcessors,
ILocalTaskDataService localTaskDataService,
ILocalLocationDataService localLocationDataService,
ILocalPushNotificationDataService localPushNotificationDataService,
IPushNotificationsService pushNotificationsService,
ILocalPictureDataService localPictureDataService, ILocalLocalizationDataService localLocalizationDataService) {
_navigationService = navigationService;
_localUserDataService = localUserDataService;
_toMobileTasksSyncer = toMobileTasksSyncer;
_queueProcessors = queueProcessors;
_localTaskDataService = localTaskDataService;
_localLocationDataService = localLocationDataService;
_localPushNotificationDataService = localPushNotificationDataService;
_pushNotificationsService = pushNotificationsService;
_localPictureDataService = localPictureDataService;
_localLocalizationDataService = localLocalizationDataService;
}
public async Task OnStart() {
await ReloadData();
await CreateDeviceIdOnFirstStartAsync();
var currentUser = _localUserDataService.GetCurrentUser();
if (currentUser.IsLoggedIn) {
Configuration.Configuration.SetEnvironment(currentUser.Environment);
await _navigationService.NavigateToViewModelAsync<MainTaskListViewModel>(true);
try {
await _toMobileTasksSyncer.SyncAllMetaData();
}
catch {
// ignored
}
}
else {
await _navigationService.NavigateToViewModelAsync<LoginViewModel>();
}
foreach (var queueProcessor in _queueProcessors) {
_ = Task.Run(async () => await queueProcessor.StartPeriodicProcessing());
}
}
public async Task OnResume() {
try {
await _toMobileTasksSyncer.SyncAllTasks(true);
}
catch {
// ignored
}
}
public async Task CreateDeviceIdOnFirstStartAsync() {
var currentPushNotificationInfo = await _localPushNotificationDataService.GetCurrentPushNotificationInfo();
if (string.IsNullOrEmpty(currentPushNotificationInfo.DeviceIdTag)) {
await _localPushNotificationDataService.SetDeviceId(Guid.NewGuid().ToString());
}
}
private async Task ReloadData() {
await _localLocalizationDataService.ReloadData();
await _localUserDataService.ReloadData();
await _localTaskDataService.ReloadData();
await _localLocationDataService.ReloadData();
await _localPictureDataService.ReloadData();
await _pushNotificationsService.ProcessReceivedMessages();
}
}
}
In this class I inject an I Enumerable of IQueueProcessors. These queueProcessors inject themselves the IQueueOrchestrators. If you need more information, please ask me anything.
I found out what was causing it. The cause is android related and is described in this stack overflow post: description of what causes this
The solution that worked for me came from this post about the same topic: solution for me
Basically the solution that worked for me was adding this to OnCreate:
// Possible work around for market launches. See https://issuetracker.google.com/issues/36907463
// for more details. Essentially, the market launches the main activity on top of other activities.
// we never want this to happen. Instead, we check if we are the root and if not, we finish.
if (!isTaskRoot()) {
final Intent intent = getIntent();
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(intent.getAction())) {
Log.w(LOG_TAG, "Main Activity is not the root. Finishing Main Activity instead of launching.");
finish();
return;
}
}

Why does my MassTransit Fault Consumer not get called?

I am using MassTransit 7.1.4.0 together with RabbitMQ 3.8.7.
I have a consumer which simply throws an exception, and I have a fault consumer which Console.WriteLine the fact that a fault occurred.
The fault consumer is not called. I also tried using a fault consumer definition. Also that does not work.
Below is the entire program.
using System;
using System.Threading.Tasks;
using GreenPipes;
using MassTransit;
using MassTransit.ConsumeConfigurators;
using MassTransit.Definition;
using Microsoft.Extensions.DependencyInjection;
namespace FaultConsumer
{
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Hello Fault Consumer!");
var services = new ServiceCollection();
services.AddMassTransit(x =>
{
x.AddConsumer<MyConsumer>(typeof(MyConsumerDefinition));
x.AddConsumer<MyFaultConsumer>();//typeof(MyFaultConsumerDefinition));
x.SetKebabCaseEndpointNameFormatter();
x.UsingRabbitMq((context, cfg) =>
{
cfg.Host("rabbitmq://localhost");
cfg.ConfigureEndpoints(context);
});
});
var serviceProvider = services.BuildServiceProvider();
var bus = serviceProvider.GetRequiredService<IBusControl>();
await bus.StartAsync();
await bus.Publish(new SubmitOrder() { DateTimeStamp = DateTime.Now });
Console.WriteLine("Press any key to exit");
await Task.Run(() => Console.ReadKey());
await bus.StopAsync();
}
}
class SubmitOrder
{
public DateTime DateTimeStamp { get; set; }
}
class MyConsumer : IConsumer<SubmitOrder>
{
public async Task Consume(ConsumeContext<SubmitOrder> context)
{
Console.WriteLine($"Attempting to consume {context.GetRetryCount()} {context.GetRetryAttempt()}");
throw new Exception(context.GetRetryCount().ToString());
}
}
class MyConsumerDefinition : ConsumerDefinition<MyConsumer>
{
public MyConsumerDefinition()
{
EndpointName = "order-service-202102081221";
ConcurrentMessageLimit = 8;
}
protected override void ConfigureConsumer(IReceiveEndpointConfigurator endpointConfigurator,
IConsumerConfigurator<MyConsumer> consumerConfigurator)
{
//endpointConfigurator.UseMessageRetry(r => r.Intervals(100, 200, 500, 800, 1000));
endpointConfigurator.UseMessageRetry(r => r.Immediate(5));
endpointConfigurator.UseInMemoryOutbox();
}
}
class MyFaultConsumer : IConsumer<Fault<MyConsumer>>
{
public async Task Consume(ConsumeContext<Fault<MyConsumer>> context)
{
Console.WriteLine("Fault");
await Task.CompletedTask;
}
}
// class MyFaultConsumerDefinition : ConsumerDefinition<MyFaultConsumer>
// {
// public MyFaultConsumerDefinition()
// {
// // override the default endpoint name
// EndpointName = "order-service-faults-202102081221";
// // limit the number of messages consumed concurrently
// // this applies to the consumer only, not the endpoint
// ConcurrentMessageLimit = 8;
// }
// protected override void ConfigureConsumer(IReceiveEndpointConfigurator endpointConfigurator,
// IConsumerConfigurator<MyFaultConsumer> consumerConfigurator)
// {
// // configure message retry with millisecond intervals
// endpointConfigurator.UseMessageRetry(r => r.Intervals(100, 200, 500, 800, 1000));
// // use the outbox to prevent duplicate events from being published
// endpointConfigurator.UseInMemoryOutbox();
// }
// }
}
Faults are published based on message type, not consumer type.
Your fault consumer should be consuming Fault<SubmitOrder>.
class MyFaultConsumer :
IConsumer<Fault<SubmitOrder>>
{
public Task Consume(ConsumeContext<Fault<SubmitOrder>> context)
{
Console.WriteLine("Fault");
return Task.CompletedTask;
}
}

Xamarin Forms run code when app goes to background (i.e. enteres sleep)

I have an app (iOS, Android, UWP) that retrieves data every minute. When the app is in the foreground I use a timer to kickoff a call to retrieve the data.
When the app enters sleep state, I want to stop using the timer to retrieve the data and use something that will run in the background and keep running as long as the app is sleeping. (It is OK if it stops if the app is terminated.
This app does not required an internet connection as the data source in within the local network the app is connected to. It appears I have the iOS and Android running using the code shown below. The challenge I am now facing is getting the UWP version going.
Code in the share code project
Interface Code (I have in a seperate file).
using System;
using System.Threading.Tasks;
namespace DependencyServiceDemos
{
public interface IBackgroundService
{
Task RunCodeInBackgroundMode(Func<Task> action, string name = "BackgroundService");
}
}
This is the code that will be called to execute in the background.
using System;
using System.Diagnostics;
using System.Timers;
using Xamarin.Forms;
namespace DependencyServiceDemos
{
public static class BackgroundCode
{
public static bool StopRunning { get; set; } = false;
public static Timer iobj_timer;
public static bool GetBackgroundData(int pi_SecondsLoop)
{
bool lb_ReturnValue = true;
// Start a timer that runs after 30 seconds.
Device.StartTimer(TimeSpan.FromSeconds(pi_SecondsLoop), () =>
{
// Do the actual request and wait for it to finish.
App.GlobalBackgroundData.MyBackgroundData = "Current Date / Time: " + DateTime.Now.ToString();
// Don't repeat the timer (we will start a new timer when the request is finished)
if (StopRunning)
{
lb_ReturnValue = false;
Debug.WriteLine("Stopping Background Process: " + App.GlobalBackgroundData.MyBackgroundData);
}
else
{
Debug.WriteLine("Background Code Processed: " + App.GlobalBackgroundData.MyBackgroundData);
}
return lb_ReturnValue;
});
return lb_ReturnValue;
}
}
}
This is the ViewModel where I update the data gathered in the background in order to update the UI.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
namespace DependencyServiceDemos
{
public class BackgroundData : INotifyPropertyChanged
{
private string is_BackgroundData = "";
public string MyBackgroundData
{
get
{
return is_BackgroundData;
}
set
{
is_BackgroundData = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In the App.xaml.cs I declare a static instance of my ViewModel as follows:
public static BackgroundData GlobalBackgroundData { get; set; }
Then I initialize the ViewModel in the app constructor:
InitializeComponent();
GlobalBackgroundData = new BackgroundData();
In the iOS application - I implement the interface as follows:
using DependencyServiceDemos.iOS;
using DependencyServiceDemos;
using System;
using System.Threading.Tasks;
using UIKit;
using Xamarin.Forms;
[assembly: Dependency(typeof(BackgroundService))]
namespace DependencyServiceDemos.iOS
{
public class BackgroundService : IBackgroundService
{
public async Task RunCodeInBackgroundMode(Func<Task> action, string name = "BackgroundService")
{
nint taskId = 0;
var taskEnded = false;
taskId = UIApplication.SharedApplication.BeginBackgroundTask(name, () =>
{
//when time is up and task has not finished, call this method to finish the task to prevent the app from being terminated
Console.WriteLine($"Background task '{name}' got killed");
taskEnded = true;
UIApplication.SharedApplication.EndBackgroundTask(taskId);
});
await Task.Factory.StartNew(async () =>
{
//here we run the actual task
Console.WriteLine($"Background task '{name}' started");
await action();
taskEnded = true;
UIApplication.SharedApplication.EndBackgroundTask(taskId);
Console.WriteLine($"Background task '{name}' finished");
});
await Task.Factory.StartNew(async () =>
{
//Just a method that logs how much time we have remaining. Usually a background task has around 180 seconds to complete.
while (!taskEnded)
{
Console.WriteLine($"Background task '{name}' time remaining: {UIApplication.SharedApplication.BackgroundTimeRemaining}");
await Task.Delay(1000);
}
});
}
}
}
I updated the FinishLaunching to register the dependency service as follows:
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
DependencyService.Register<IBackgroundService, BackgroundService>();
return base.FinishedLaunching(app, options);
}
In the Android project, I implemented the interface as follows:
using Android.Content;
using Android.OS;
using DependencyServiceDemos;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Xamarin.Forms;
[assembly: Dependency(typeof(IBackgroundService))]
namespace DependencyServiceDemos.Droid
{
public class BackgroundService : IBackgroundService
{
public async Task RunCodeInBackgroundMode(Func<Task> action, string name = "BackgroundService")
{
var powerManager = (PowerManager)Android.App.Application.Context.GetSystemService(Context.PowerService);
var wakeLock = powerManager.NewWakeLock(WakeLockFlags.Partial,
name);
//acquire a partial wakelock. This prevents the phone from going to sleep as long as it is not released.
wakeLock.Acquire();
var taskEnded = false;
await Task.Factory.StartNew(async () =>
{
//here we run the actual code
Console.WriteLine($"Background task '{name}' started");
await action();
Console.WriteLine($"Background task '{name}' finished");
wakeLock.Release();
taskEnded = true;
});
await Task.Factory.StartNew(async () =>
{
//just a method to keep track of how long the task runs
var stopwatch = new Stopwatch();
stopwatch.Start();
while (!taskEnded)
{
Console.WriteLine($"Background '{name}' task with wakelock still running ({stopwatch.Elapsed.TotalSeconds} seconds)");
await Task.Delay(1000);
}
stopwatch.Stop();
});
}
}
}
Then I registered the dependency service in the MainActivity OnCreated event as the last line with the following code:
DependencyService.Register<IBackgroundService, BackgroundService>();
So that all seems to work well. Now I am trying to implement the interface in UWP using the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DependencyServiceDemos.UWP.Services
{
public class BackgroundService : IBackgroundService
{
public async Task RunCodeInBackgroundMode(Func<Task> action, string name = "BackgroundService")
{
await Task.Factory.StartNew(async () =>
{
//here we run the actual code
Console.WriteLine($"Background task '{name}' started");
await action();
Console.WriteLine($"Background task '{name}' finished");
});
}
}
}
I also initialize the service in the MainPage constructor of the UWP project as follows:
DependencyService.Register<IBackgroundService, BackgroundService>();
So from what I can tell the code in the service executes but the following line of code does not appear to execute the BackgroundCode.GetBackgroundData in the static class of my shared code.
Two things:
1. Is there anything 'wrong' with my implementation for iOS or Android that might get it kicked by the corresponding stores?
2. Any idea why my UWP code is not working?
await action();

Alternative solution to HostingEnvironment.QueueBackgroundWorkItem in .NET Core

We are working with .NET Core Web Api, and looking for a lightweight solution to log requests with variable intensity into database, but don't want client's to wait for the saving process.
Unfortunately there's no HostingEnvironment.QueueBackgroundWorkItem(..) implemented in dnx, and Task.Run(..) is not safe.
Is there any elegant solution?
As #axelheer mentioned IHostedService is the way to go in .NET Core 2.0 and above.
I needed a lightweight like for like ASP.NET Core replacement for HostingEnvironment.QueueBackgroundWorkItem, so I wrote DalSoft.Hosting.BackgroundQueue which uses.NET Core's 2.0 IHostedService.
PM> Install-Package DalSoft.Hosting.BackgroundQueue
In your ASP.NET Core Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddBackgroundQueue(onException:exception =>
{
});
}
To queue a background Task just add BackgroundQueue to your controller's constructor and call Enqueue.
public EmailController(BackgroundQueue backgroundQueue)
{
_backgroundQueue = backgroundQueue;
}
[HttpPost, Route("/")]
public IActionResult SendEmail([FromBody]emailRequest)
{
_backgroundQueue.Enqueue(async cancellationToken =>
{
await _smtp.SendMailAsync(emailRequest.From, emailRequest.To, request.Body);
});
return Ok();
}
QueueBackgroundWorkItem is gone, but we've got IApplicationLifetime instead of IRegisteredObject, which is being used by the former one. And it looks quite promising for such scenarios, I think.
The idea (and I'm still not quite sure, if it's a pretty bad one; thus, beware!) is to register a singleton, which spawns and observes new tasks. Within that singleton we can furthermore register a "stopped event" in order to proper await still running tasks.
This "concept" could be used for short running stuff like logging, mail sending, and the like. Things, that should not take much time, but would produce unnecessary delays for the current request.
public class BackgroundPool
{
protected ILogger<BackgroundPool> Logger { get; }
public BackgroundPool(ILogger<BackgroundPool> logger, IApplicationLifetime lifetime)
{
if (logger == null)
throw new ArgumentNullException(nameof(logger));
if (lifetime == null)
throw new ArgumentNullException(nameof(lifetime));
lifetime.ApplicationStopped.Register(() =>
{
lock (currentTasksLock)
{
Task.WaitAll(currentTasks.ToArray());
}
logger.LogInformation(BackgroundEvents.Close, "Background pool closed.");
});
Logger = logger;
}
private readonly object currentTasksLock = new object();
private readonly List<Task> currentTasks = new List<Task>();
public void SendStuff(Stuff whatever)
{
var task = Task.Run(async () =>
{
Logger.LogInformation(BackgroundEvents.Send, "Sending stuff...");
try
{
// do THE stuff
Logger.LogInformation(BackgroundEvents.SendDone, "Send stuff returns.");
}
catch (Exception ex)
{
Logger.LogError(BackgroundEvents.SendFail, ex, "Send stuff failed.");
}
});
lock (currentTasksLock)
{
currentTasks.Add(task);
currentTasks.RemoveAll(t => t.IsCompleted);
}
}
}
Such a BackgroundPool should be registered as a singleton and can be used by any other component via DI. I'm currently using it for sending mails and it works fine (tested mail sending during app shutdown too).
Note: accessing stuff like the current HttpContext within the background task should not work. The old solution uses UnsafeQueueUserWorkItem to prohibit that anyway.
What do you think?
Update:
With ASP.NET Core 2.0 there's new stuff for background tasks, which get's better with ASP.NET Core 2.1: Implementing background tasks in .NET Core 2.x webapps or microservices with IHostedService and the BackgroundService class
You can use Hangfire (http://hangfire.io/) for background jobs in .NET Core.
For example :
var jobId = BackgroundJob.Enqueue(
() => Console.WriteLine("Fire-and-forget!"));
Here is a tweaked version of Axel's answer that lets you pass in delegates and does more aggressive cleanup of completed tasks.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
namespace Example
{
public class BackgroundPool
{
private readonly ILogger<BackgroundPool> _logger;
private readonly IApplicationLifetime _lifetime;
private readonly object _currentTasksLock = new object();
private readonly List<Task> _currentTasks = new List<Task>();
public BackgroundPool(ILogger<BackgroundPool> logger, IApplicationLifetime lifetime)
{
if (logger == null)
throw new ArgumentNullException(nameof(logger));
if (lifetime == null)
throw new ArgumentNullException(nameof(lifetime));
_logger = logger;
_lifetime = lifetime;
_lifetime.ApplicationStopped.Register(() =>
{
lock (_currentTasksLock)
{
Task.WaitAll(_currentTasks.ToArray());
}
_logger.LogInformation("Background pool closed.");
});
}
public void QueueBackgroundWork(Action action)
{
#pragma warning disable 1998
async Task Wrapper() => action();
#pragma warning restore 1998
QueueBackgroundWork(Wrapper);
}
public void QueueBackgroundWork(Func<Task> func)
{
var task = Task.Run(async () =>
{
_logger.LogTrace("Queuing background work.");
try
{
await func();
_logger.LogTrace("Background work returns.");
}
catch (Exception ex)
{
_logger.LogError(ex.HResult, ex, "Background work failed.");
}
}, _lifetime.ApplicationStopped);
lock (_currentTasksLock)
{
_currentTasks.Add(task);
}
task.ContinueWith(CleanupOnComplete, _lifetime.ApplicationStopping);
}
private void CleanupOnComplete(Task oldTask)
{
lock (_currentTasksLock)
{
_currentTasks.Remove(oldTask);
}
}
}
}
I know this is a little late, but we just ran into this issue too. So after reading lots of ideas, here's the solution we came up with.
/// <summary>
/// Defines a simple interface for scheduling background tasks. Useful for UnitTesting ASP.net code
/// </summary>
public interface ITaskScheduler
{
/// <summary>
/// Schedules a task which can run in the background, independent of any request.
/// </summary>
/// <param name="workItem">A unit of execution.</param>
[SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
void QueueBackgroundWorkItem(Action<CancellationToken> workItem);
/// <summary>
/// Schedules a task which can run in the background, independent of any request.
/// </summary>
/// <param name="workItem">A unit of execution.</param>
[SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
}
public class BackgroundTaskScheduler : BackgroundService, ITaskScheduler
{
public BackgroundTaskScheduler(ILogger<BackgroundTaskScheduler> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogTrace("BackgroundTaskScheduler Service started.");
_stoppingToken = stoppingToken;
_isRunning = true;
try
{
await Task.Delay(-1, stoppingToken);
}
catch (TaskCanceledException)
{
}
finally
{
_isRunning = false;
_logger.LogTrace("BackgroundTaskScheduler Service stopped.");
}
}
public void QueueBackgroundWorkItem(Action<CancellationToken> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
if (!_isRunning)
throw new Exception("BackgroundTaskScheduler is not running.");
_ = Task.Run(() => workItem(_stoppingToken), _stoppingToken);
}
public void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
if (!_isRunning)
throw new Exception("BackgroundTaskScheduler is not running.");
_ = Task.Run(async () =>
{
try
{
await workItem(_stoppingToken);
}
catch (Exception e)
{
_logger.LogError(e, "When executing background task.");
throw;
}
}, _stoppingToken);
}
private readonly ILogger _logger;
private volatile bool _isRunning;
private CancellationToken _stoppingToken;
}
The ITaskScheduler (which we already defined in our old ASP.NET client code for UTest test purposes) allows a client to add a background task. The main purpose of the BackgroundTaskScheduler is to capture the stop cancellation token (which is own by the Host) and to pass it into all the background Tasks; which by definition, runs in the System.Threading.ThreadPool so there is no need to create our own.
To configure Hosted Services properly see this post.
Enjoy!
I have used Quartz.NET (does not require SQL Server) with the following extension method to easily set up and run a job:
public static class QuartzUtils
{
public static async Task<JobKey> CreateSingleJob<JOB>(this IScheduler scheduler,
string jobName, object data) where JOB : IJob
{
var jm = new JobDataMap { { "data", data } };
var jobKey = new JobKey(jobName);
await scheduler.ScheduleJob(
JobBuilder.Create<JOB>()
.WithIdentity(jobKey)
.Build(),
TriggerBuilder.Create()
.WithIdentity(jobName)
.UsingJobData(jm)
.StartNow()
.Build());
return jobKey;
}
}
Data is passed as an object that must be serializable. Create an IJob that processes the job like this:
public class MyJobAsync :IJob
{
public async Task Execute(IJobExecutionContext context)
{
var data = (MyDataType)context.MergedJobDataMap["data"];
....
Execute like this:
await SchedulerInstance.CreateSingleJob<MyJobAsync>("JobTitle 123", myData);
The original HostingEnvironment.QueueBackgroundWorkItem was a one-liner and very convenient to use.
The "new" way of doing this in ASP Core 2.x requires reading pages of cryptic documentation and writing considerable amount of code.
To avoid this you can use the following alternative method
public static ConcurrentBag<Boolean> bs = new ConcurrentBag<Boolean>();
[HttpPost("/save")]
public async Task<IActionResult> SaveAsync(dynamic postData)
{
var id = (String)postData.id;
Task.Run(() =>
{
bs.Add(Create(id));
});
return new OkResult();
}
private Boolean Create(String id)
{
/// do work
return true;
}
The static ConcurrentBag<Boolean> bs will hold a reference to the object, this will prevent garbage collector from collecting the task after the controller returns.

How do I get CreateUsingInMemory() to work with MassTransit?

Here's my entire code. I think the test should pass, but it fails. I've (unsuccessfully) tried using some of the overloads to Consumer.
using MassTransit;
using NUnit.Framework;
using System.Threading.Tasks;
namespace MassTransitTests
{
public class Message
{
}
public class MessageConsumer : IConsumer<Message>
{
public static int ConsumedCount
{
get;
private set;
}
public Task Consume(ConsumeContext<Message> context)
{
ConsumedCount++;
return Task.FromResult(0);
}
}
[TestFixture]
public class MassTransitTest
{
[Test]
public async Task BasicTestAsync()
{
// Arrange
var control = Bus.Factory.CreateUsingInMemory(configure =>
{
configure.ReceiveEndpoint("myQueue", endpoint =>
{
endpoint.Consumer<MessageConsumer>();
});
});
// Act
using (var handle = control.Start())
{
await control.Publish(new Message());
await control.Publish(new Message());
}
// Assert
Assert.That(MessageConsumer.ConsumedCount, Is.EqualTo(2));
}
}
}
Their documentation shows this, which is what I'm doing:
var busControl = Bus.Factory.CreateUsingInMemory(cfg =>
{
cfg.ReceiveEndpoint("queue_name", ep =>
{
//configure the endpoint
})
});
What am I doing wrong/what I do need to change in my Arrange/Act to get my Assert to work?
After digging through their tests, I found what I was missing:
[1] You need* to await BusHandle.Ready, which I wasn't doing. *(The test works without this - at least the first time I ran it, but that may just be a race condition working in my favor....)
[2] The calls to Publish apparently complete whenever the bus has received the message I'm guessing - not when the handlers/consumers of the message have completed their work. Therefore you need to notify the calling code that the handlers have finished if that's what you're testing. Here's one way to do this - use TaskCompletionSource<T> (similar to what I found in their codebase). Obviously I may not have been perfect in my thread-safety and my lock usage is a bit sledge-hammer-esque, but this illustrates the point:
using MassTransit;
using NUnit.Framework;
using System.Threading.Tasks;
namespace MassTransitTests
{
public class Message
{
}
public class MessageConsumer : IConsumer<Message>
{
public static int TargetConsumedCount
{
get { return _targetConsumedCount; }
set
{
lock (_lock)
{
_targetConsumedCount = value;
CheckTargetReached();
}
}
}
private static void CheckTargetReached()
{
if (_consumedCount >= TargetConsumedCount)
{
_targetReached.SetResult(true);
}
}
public static Task<bool> TargetReached { get; private set; }
private static int _consumedCount;
private static int _targetConsumedCount;
private static TaskCompletionSource<bool> _targetReached;
private static object _lock;
static MessageConsumer()
{
_lock = new object();
_targetReached = new TaskCompletionSource<bool>();
TargetReached = _targetReached.Task;
}
public Task Consume(ConsumeContext<Message> context)
{
lock (_lock)
{
_consumedCount++;
CheckTargetReached();
}
return Task.FromResult(0);
}
}
[TestFixture]
public class MassTransitTest
{
[Test]
public async Task BasicTestAsync()
{
// Arrange
var control = Bus.Factory.CreateUsingInMemory(configure =>
{
configure.ReceiveEndpoint("myQueue", endpoint =>
{
endpoint.Consumer<MessageConsumer>();
});
});
using (var handle = control.Start())
{
await handle.Ready; // [1]
// Act
await control.Publish(new Message());
await control.Publish(new Message());
// Assert
MessageConsumer.TargetConsumedCount = 2;
await MessageConsumer.TargetReached; // [2]
}
}
}
}

Categories