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);
}
}
Related
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
}
}
We start using CancellationToken in out app a lot, so we have to change exception handling correspondingly:
class Program
{
static async Task Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource(100);
await DoJob(cts.Token);
Console.WriteLine("Successfully finished");
}
private static async Task DoJob(CancellationToken ct)
{
try
{
await Task.Delay(1000, ct);
}
catch (Exception e) when(!(e is OperationCanceledException))
{
Console.WriteLine("Do cleanup in case of error.");
}
}
}
The idea behind this code is that if someone use catch(Exception e) (please do not blame me for this) and forgot to exclude CancellationToken, an error handling is executed, for example, there is a log that operation failed. But it is not true, id doesn't fail, it just has been canceled. And cancellation should be handled differently then failure.
It seems to me like a big boilerplate to write practically in every general catch
catch (Exception e) when(!(e is OperationCanceledException))
Is there some more robust solution with less boilerplate?
You could create a method that accepts a Func<Task> and catches the exception(s), e.g.:
class Program
{
static async Task Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource(100);
await GeneralDoJobAndCatchException(() => DoJob(cts.Token));
Console.WriteLine("Successfully finished");
}
private static async Task GeneralDoJobAndCatchException(Func<Task> func)
{
try
{
await func();
}
catch (OperationCanceledException) { }
catch (Exception e)
{
Console.WriteLine("Do error handling");
}
}
private static async Task DoJob(CancellationToken ct)
{
await Task.Delay(1000, ct);
}
}
We're having the exact same problem. Mainly there is a while-loop that checks for the CancellationToken but you've to catch this exception.
We created the following extension method:
public static async Task<TaskStatus> HideCancellationException(this Task task)
{
try
{
await task;
return task.Status;
}
catch (OperationCanceledException)
{
return TaskStatus.Canceled;
}
}
Having this extension method allows to change this code:
while (!cancellationToken.IsCancellationRequested)
{
// do stuff here...
try
{
await Task.Delay(..., cancellationToken);
}
catch (OperationCanceledException)
{
// expected
}
}
to something like that:
while (!cancellationToken.IsCancellationRequested)
{
// Do stuff here.
await Task.Delay(..., cancellationToken).HideCancellationException();
}
Keep in mind that there is explicitly no overload for Task<T> because the return value in case of cancellation is default. You can't distinguish between default as normal task result and default as result of cancellation. In that case it's better to catch the exception.
You could get rid of the try-catch block altogether by awaiting indirectly with Task.WhenAny, and then querying the status of the completed task:
private static async Task DoJob(CancellationToken ct)
{
var completedTask = await Task.WhenAny(Task.Delay(1000, ct));
if (completedTask.IsFaulted)
{
Console.WriteLine("Error: " + completedTask.Exception.InnerException);
}
else if (completedTask.IsCanceled)
{
// Do nothing
}
else // Success
{
// Do nothing
}
}
Here's what I have. There's more to the code but I just put in this snippet to make the question smaller.
public async Task GetCards(CancellationTokenSource cts)
{
try
{
while (true)
{
await CheckCardAvailability(cts);
}
}
catch (TaskCanceledException)
{
cts.Dispose();
}
}
private async Task CheckCardAvailability(CancellationTokenSource cts)
{
if (Counts.selectedCardCount == 0)
{
CardCountZeroMessages();
SetMessageView(false);
await Task.Delay(500, cts.Token);
return;
}
if (XYX == 0)
{
// other code
await Task.Delay(500, cts.Token);
return;
}
}
I just have a simple return here but should I do this differently as the return type is Task? Note that I added the return as I have many of these if conditions and I didn't want to do a lot of if else if else if ..
As mentioned by #PanagiotisKanavos, you might consider changing your code to:
public async Task GetCards(CancellationTokenSource cts)
{
try
{
while (true)
{
CheckCardAvailability(cts);
await Task.Delay(500, cts.Token); // or something with a timer
}
}
catch (TaskCanceledException)
{
cts.Dispose();
}
}
private /*async Task*/void CheckCardAvailability(CancellationTokenSource cts)
{
if (Counts.selectedCardCount == 0)
{
CardCountZeroMessages();
SetMessageView(false);
// await Task.Delay moved outside
}
if (XYX == 0)
{
// other code
// await Task.Delay moved outside
}
}
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 the following code in Xamarin (tested in ios):
private static async Task<string> TaskWithException()
{
return await Task.Factory.StartNew (() => {
throw new Exception ("Booo!");
return "";
});
}
public static async Task<string> RunTask()
{
try
{
return await TaskWithException ();
}
catch(Exception ex)
{
Console.WriteLine (ex.ToString());
throw;
}
}
Invoking this as await RunTask(), does throw the exception from the TaskWithException method, but the catch method in RunTask is never hit. Why is that? I would expect the catch to work just like in Microsoft's implementation of async/await. Am I missing something?
You cant await a method inside of a constructor, so thats why you can't catch the Exception.
To catch the Exception you must await the operation.
I have here two ways of calling an async method from the constructor:
1. ContinueWith solution
RunTask().ContinueWith((result) =>
{
if (result.IsFaulted)
{
var exp = result.Exception;
}
});
2. Xamarin Forms
Device.BeginInvokeOnMainThread(async () =>
{
try
{
await RunTask();
}
catch (Exception ex)
{
Console.WriteLine (ex.ToString());
}
});
3. iOS
InvokeOnMainThread(async () =>
{
try
{
await RunTask();
}
catch (Exception ex)
{
Console.WriteLine (ex.ToString());
}
});