I realized that background services blocks main thread and suddenly it makes latency in my main thread.I can't find useful resources about how to make backgroundservices to not make latency and block main thread.Any suggestions?
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(DoWork, null, 0, 10000);
Initialize().GetAwaiter();
return Task.CompletedTask;
}
private async void DoWork(object state)
{
try
{
//some logic
await testService.DoWorkAsync();
}
catch (Exception ex)
{
Log.Error(ex, "TestBackgroundService DoWork throws ");
}
}
private async Task Initialize()
{
while (true)
{
try
{
if (IsEnabled)
{
//some logic
await testService.PrintAsync();
IsEnabled = false;
}
else
{
//some logic
await testService.CloseAsync();
if (condition)
{
IsEnabled = true;
}
}
await Task.Delay(1000);
}
catch (Exception ex)
{
Log.Error($"ExecuteAsync throws {ex.ToString()}");
}
}
}
You have to use it the right way and I think scoped background task is what you want:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio#consuming-a-scoped-service-in-a-background-task
Here you have a ExecuteAsync Method and you could use it like this:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
//your code
await DoWork(stoppingToken);
Task.Delay(1000);
}
}
If it is possible, you can create an Controller for the background jobs.
From your code, you can simply invoke the controller action using an HttpClient method.
private async Task ExecuteJobAsync(string jobName)
{
// background job
string uri = $"http://localhost:21454/api/jobs/{jobName}";
using (HttpClient client = new HttpClient())
{
using (HttpContent content = new StringContent(string.Empty, Encoding.UTF8, "application/json"))
{
using (HttpResponseMessage response = await httpClient.PostAsync(uri, content).ConfigureAwait(false))
{
}
}
}
}
Invoking background job:
private async Task Initialize()
{
while (true)
{
try
{
if (IsEnabled)
{
await ExecuteJobAsync("print");
IsEnabled=false;
}
else
{
//some logic
await ExecuteJobAsync("close");
if(condition){
IsEnabled=true;
}
}
await Task.Delay(1000);
}
catch (Exception ex)
{
Log.Error($"ExecuteAsync throws {ex.ToString()}");
}
}
}
Jobs controller:
[Route("api/jobs")]
public class JobsController : Controller
{
[HttpPost("print")]
public void PrintAction()
{
// code
}
[HttpPost("close")]
public void CloseAction()
{
// code
}
}
Related
I have something similar to this:
public async Task Task1()
{
await using var transaction = await _context.Database.BeginTransactionAsync();
try
{
//Code goes here
await OtherTask();
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}
public async Task OtherTask()
{
await using var transaction = await _context.Database.BeginTransactionAsync();
try
{
//Code goes here
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}
The problem that I'm having is that when I call OtherTask() and it tries to do the BeginTransactionAsync(), it says that one transaction already exist. Is there anyway I can fix this? I want to be able to call multiple functions, that when executed alone can have their own transaction, but when being called from a function which already has a transaction, to use that instead.
The easiest way to fix this is to refactor the logic into a separate method:
public async Task Task1()
{
await using var transaction = await _context.Database.BeginTransactionAsync();
try
{
//Code goes here
await OtherTask(transaction);
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}
public async Task OtherTask(Transaction transaction)
{
//Code goes here
}
public async Task OtherTask()
{
await using var transaction = await _context.Database.BeginTransactionAsync();
try
{
await OtherTask(transaction);
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}
I am assuming you are using EntityFramework Core.
DbContext.Database has a property CurrentTransation that will be null if neither BeginTransactionAsync nor UseTransaction have been called before.
public async Task Task1()
{
await using var transaction = _context.Database.CurrentTransaction == null
? await _context.Database.BeginTransactionAsync()
: _context.Database.UseTransaction(_context.Database.CurrentTransaction);
try
{
//Code goes here
await OtherTask();
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}
public async Task OtherTask()
{
await using var transaction = _context.Database.CurrentTransaction == null
? await _context.Database.BeginTransactionAsync()
: _context.Database.UseTransaction(_context.Database.CurrentTransaction);
try
{
//Code goes here
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}
Good day, I'm trying to create a general purpose task queue service that executes in the background using BackgroundService. I avoided using a Delegate function Func<T1,T2, OuT> as input to EnqueueTask(Task<ResponseHelper> newTask) method because I wanted a generic solution, so I opted to pass a Task<ResponseHelper> instead. But this solution does not allow me to re-enqueue a failed task inside ExecuteAsync(CancellationToken stoppingToken) because the task returns an instance of ResponseHelper which is not a copy of the failed task. Kindly help me correct the code I have to return a dequeued task from DequeueTaskAsync(CancellationToken cancellationToken) function instead of an instance of ResponseHelper.
public interface ITaskQueueHelper
{
void EnqueueTask(Task<ResponseHelper> newTask);
Task<ResponseHelper> DequeueTaskAsync(CancellationToken cancellationToken);
}
public class TaskQueueHelper : ITaskQueueHelper
{
private readonly SemaphoreSlim signal;
private readonly ConcurrentQueue<Task<ResponseHelper>> taskQueue;
public TaskQueueHelper()
{
signal = new SemaphoreSlim(0);
taskQueue = new ConcurrentQueue<Task<ResponseHelper>>();
}
public void EnqueueTask(Task<ResponseHelper> newTask)
{
if (newTask == null)
{
throw new ArgumentNullException(nameof(newTask));
}
taskQueue.Enqueue(newTask);
signal.Release();
}
public async Task<ResponseHelper> DequeueTaskAsync(CancellationToken cancellationToken)
{
await signal.WaitAsync(cancellationToken);
taskQueue.TryDequeue(out var currentTask);
/*I need to return currentTask here, instead of an instance of ResponseHelper*/
return await currentTask;
}
}
public class TaskQueueService : BackgroundService
{
private readonly ITaskQueueHelper taskQueue;
private readonly ILogger<TaskQueueService> logger;
public TaskQueueService(
ITaskQueueHelper _taskQueue,
ILogger<TaskQueueService> _logger)
{
logger = _logger;
taskQueue = _taskQueue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
ResponseHelper response = await taskQueue.DequeueTaskAsync(stoppingToken);
try
{
if (!response.Status.Equals(ResultCode.Success))
{
/*I need to re-enqueue a failed task here*/
//taskQueue.EnqueueTask(currentTask);
}
}
catch (Exception e)
{
logger.LogError(e, $"Error occurred executing {nameof(TaskQueueService)}");
}
}
}
}
for retrying you could do:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
ResponseHelper response = await taskQueue.DequeueTaskAsync(stoppingToken);
try
{
if (!response.Status.Equals(ResultCode.Success))
{
// Retry the task.
response = await taskQueue.DequeueTaskAsync(stoppingToken);
}
}
catch (Exception e)
{
logger.LogError(e, $"Error occurred executing {nameof(TaskQueueService)}");
}
}
}
I'm running into the issue where the task being executed below runs asynchronously but is unable to be cancelled when the serializer reads the memory stream. When the user makes the cancellation request (by pressing a cancel button), a cancellation (the method cancel() is called fromt he token) is made but the task continues.
Service Class:
Async method called from LoadHelper() in Main class
public async void StartTask(Action callback, CancellationToken token)
{
await Task.Run(callback, token);
}
Main Class:
private void LoadHelper()
{
_services.GetInstance<IThreadService>().StartTask(
() => LoadHelperAsync(), _cancelService.Token);
}
Method being run async
private void LoadHelperAsync()
{
var serializer = new DataContractSerializer(typeof(UserDatabase));
string selectedDir;
ComparisonResult = null;
_services.GetInstance<IOpenFileService>().DisplayOpenFileDialog(
"Select a saved comparison",
"Comparison File(*.xml; *.cmp)|*.xml;*.cmp",
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
out selectedDir);
if (!string.IsNullOrEmpty(selectedDir))
{
_dispatchService.BeginInvoke(() => IsExecuting = true);
using (FileStream fileStream = new FileStream(selectedDir, FileMode.Open, FileAccess.Read))
using (DeflateStream compressedStream = new DeflateStream(fileStream, CompressionMode.Decompress))
using (BufferedStream regularStream = new BufferedStream(fileStream))
{
Stream memoryStream;
//Use filename to determine compression
if (selectedDir.EndsWith(".cmp", true, null))
{
memoryStream = compressedStream;
}
else
{
memoryStream = regularStream;
}
Report("Loading comparison");
Report(0);
IsExecuting = true;
ComparisonResult = (UserDatabase)serializer.ReadObject(memoryStream);
memoryStream.Close();
fileStream.Close();
IsExecuting = false;
Report("Comparison loaded");
}
_dispatchService.BeginInvoke(() => IsExecuting = false);
_dispatchService.BeginInvoke(() => ViewResults.ExecuteIfAble());
}
else
{
Report("No comparison loaded");
}
Cancellation code:
This command is binded to a "cancel" button in the view.
CancelCompare = new Command(o => _cancelService.Cancel(), o => IsExecuting);
From the CancellationService class
public class CancellationService : ICancellationService, IDisposable
{
private CancellationTokenSource _tokenSource;
public CancellationService()
{
Reset();
}
public CancellationToken Token { get; private set; }
public void Cancel()
{
_tokenSource.Cancel();
}
public void Reset()
{
_tokenSource = new CancellationTokenSource();
Token = _tokenSource.Token;
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_tokenSource.Cancel();
_tokenSource.Dispose();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
Calling _tokenSource.Cancel(); won't do anything to a running task. A cancellation token interrupts task execution only if it was canceled before Task.Run
Take a look:
static void Main(string[] args)
{
using (var tokenSource = new CancellationTokenSource())
{
var aTask = StartTask(() =>
{
while (true)
{
Console.WriteLine("Nothing is going to happen.");
// Some long operation
Thread.Sleep(1000);
}
}, tokenSource.Token);
tokenSource.Cancel();
aTask.Wait();
}
Console.ReadKey();
}
static async Task StartTask(Action callback, CancellationToken cancellationToken)
{
await Task.Run(callback, cancellationToken);
}
If you want to cancel the execution, you should check the cancellation token by yourself, there is no any magic which cancels the task for you:
static void Main(string[] args)
{
using (var tokenSource = new CancellationTokenSource())
{
var aTask = StartTask(cancellationToken =>
{
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
Console.WriteLine("Will stop before that if canceled.");
// Some long operation
// Also pass the token all way down
Task.Delay(1000, cancellationToken).Wait();
}
}, tokenSource.Token);
// Try 0, 500, 1500 to see the difference
Thread.Sleep(1500);
tokenSource.Cancel();
try
{
aTask.Wait();
}
catch (Exception ex)
{
// AggregateException with OperationCanceledException
Console.WriteLine("Task was canceled.");
Console.WriteLine(ex.ToString());
}
}
Console.ReadKey();
}
static async Task StartTask(Action<CancellationToken> callback, CancellationToken cancellationToken)
{
await Task.Run(() => callback(cancellationToken), cancellationToken);
}
Note that this code only illustrates how the task works, never write anything like this in production.
I have two tasks that invoked async.
await Task.WhenAll (
ServerCommandWrapper (DoSomething1),
ServerCommandWrapper (DoSomething2)
);
Server Command Wrapper invoke my delegate and await it.
protected virtual async Task ServerCommandWrapper(Func<Task> action)
{
if (IsBusy) {
return;
}
try {
IsBusy = true;
await action();
} catch (System.Exception ex) {
ReportError (ex);
} finally {
IsBusy = false;
}
}
And my methods are:
protected virtual async Task DoSomething1 ()
{
await Connect();
}
protected virtual async Task DoSomething2()
{
Data = await GetSomethingAsync();
}
When I try to start two methods async It just start first one in array. What Im doing wrong?
I have written a Windows Service project which hosts a long-running message pump task which is meant to run for the duration of the service. When the service starts, it starts the task. When the service stops, it stops the task:
void OnStart()
{
MessagePump.Start();
}
void OnStop()
{
MessagePump.Stop();
}
Where MessagePump.Start does a Task.Factory.StartNew, and MessagePump.Stop signals the task to stop and does a Wait().
So far so good, but I'm wondering how best to handle faults. If the task has an unhandled exception, I'd want the service to stop but since nothing is typically Wait-ing on the task, I imagine it'll just sit doing nothing. How can I elegantly handle this situation?
UPDATE:
The consensus seems to be using 'await' or ContinueWith. Here is how I'm currently coding my Start method to use this:
public async static void Start()
{
this.state = MessagePumpState.Running;
this.task = Task.Factory.StartNew(() => this.ProcessLoop(), TaskCreationOptions.LongRunning);
try
{
await this.task;
}
catch
{
this.state = MessagePumpState.Faulted;
throw;
}
}
Make you MessagePump.Start() method return the task. Then
MessagePump.Start().ContinueWith(t =>
{
// handle exception
},
TaskContinuationOptions.OnlyOnFaulted);
UPDATE:
I would do the next:
private MessagePump _messagePump;
async void OnStart()
{
this._messagePump = new MessagePump();
try
{
// make Start method return the task to be able to handle bubbling exception here
await _messagePump.Start();
}
catch (Exception ex)
{
// log exception
// abort service
}
}
void OnStop()
{
_messagePump.Stop();
}
public enum MessagePumpState
{
Running,
Faulted
}
public class MessagePump
{
private CancellationTokenSource _cancallationTokenSrc;
private MessagePumpState _state;
public async Task Start()
{
if (_cancallationTokenSrc != null)
{
throw new InvalidOperationException("Task is already running!");
}
this._state = MessagePumpState.Running;
_cancallationTokenSrc = new CancellationTokenSource();
var task = Task.Factory.StartNew(() => this.ProcessLoop(_cancallationTokenSrc.Token), _cancallationTokenSrc.Token);
try
{
await task;
}
catch
{
this._state = MessagePumpState.Faulted;
throw;
}
}
public void Stop()
{
if (_cancallationTokenSrc != null)
{
_cancallationTokenSrc.Cancel();
_cancallationTokenSrc = null;
}
}
public void ProcessLoop(CancellationToken token)
{
// check if task has been canceled
while (!token.IsCancellationRequested)
{
Console.WriteLine(DateTime.Now);
Thread.Sleep(1000);
}
}
}
You can try something like this :
void OnStart()
{
MessagePump.StartAsync();
MessagePump.ErrorEvent += OnError();
}
Then your StartAsync will look something like:
public async Task StartAsync()
{
// your process
// if error, send event to messagePump
}
And if you decide to use Tasks, then it is better to use Task.Run and not Task.Factory.StartNew()