Complete task in Action without awaiting - task gets cancelled - asp.net-core

I have an async action, that completes a task that i need to wait for, before returning response to the cliend, but also a task that a want to run, but i don't care about the result.
[HttpPost]
[Route("api/update")]
public async Task Update(Competence competence)
{
await _competenceService.Update(competence);
_userService.DoNotWaitForMe(competence.Year);
}
Both _competenceService and _userService classes are injected into controller as Scoped via DI and both its methods are awaitable. I also use Entity framework and inject context class
// Startup.cs
services.AddScoped<ICompetenceService, CompetenceService>();
services.AddScoped<IUserService, UserService>();
services.AddDbContext<DbContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DbContext"));
});
// CompetenceService.cs
async Task Update(Competence competence)
// UserService.cs
async Task DoNotWaitForMe(int Year)
While the awaited Update method completes as expected (or throws Exception on exception), the later never finishes, and throws exception
System.Threading.Tasks.TaskCanceledException: A task was canceled. at
Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenDbConnectionAsync(Boolean
errorsExpected, CancellationToken cancellationToken) at
Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenDbConnectionAsync(Boolean
errorsExpected, CancellationToken cancellationToken) at
Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenAsync(CancellationToken
cancellationToken, Boolean errorsExpected) at
Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject
parameterObject, CancellationToken cancellationToken) at
Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.AsyncQueryingEnumerable1.AsyncEnumerator.MoveNextAsync()
at
Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1
source, CancellationToken cancellationToken) at
Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1
source, CancellationToken cancellationToken)
and sometimes also
System.ObjectDisposedException: Cannot access a disposed object. A
common cause of this error is disposing a context that was resolved
from dependency injection and then later trying to use the same
context instance elsewhere in your application. This may occur if you
are calling Dispose() on the context, or wrapping the context in a
using statement. If you are using dependency injection, you should
let the dependency injection container take care of disposing context
instances.
I would like to prevent DI (or whatever is responsible) from cancelling the Task, and disposing all the objects untill this async task is completed. Is there any way?

Simply, you can't just not await the task. The EF context has a scoped lifetime: it's instantiated when the request starts and disposed when the request finishes. If you return the response, before the work being done on the context is finished, then the context will be disposed, and an ObjectDisposedException will be thrown.
It's pretty much always wrong to not await tasks, especially in a web app. It is not the same as backgrounding. If you want to do something in the background, then you need to schedule it on an actual background service. That could be anything really: a separate process, a console app, an Azure Function, or a hosted service. The long and short is that you need to do the work on something actually intended to process tasks in the background, not just fire-and-forget a task.

Related

How to resolve exception: The current thread is not associated with the Dispatcher

I am getting this exception in a Blazor Server-Side app when a UI component is called from an async method. More specifically, the app is getting data from the database, and when it is done, it calls a Toast component at which time the exception is thrown:
This is the code that throws the exception:
ToastModel model = new ToastModel() { Content = message.MessageToShow, Timeout = message.Timeout};
this.ToastObj.Show(model);
This is the exception:
[2021-01-19T11:35:49.358Z] Error: System.InvalidOperationException: The current thread is not associated with the Dispatcher. Use InvokeAsync() to switch execution to the Dispatcher when triggering rendering or component state.
at Microsoft.AspNetCore.Components.Dispatcher.AssertAccess()
at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(Int32 componentId, RenderFragment renderFragment)
at Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()
at Syncfusion.Blazor.Notifications.SfToast.Show(ToastModel toastModel)
at CDS.Pages.Accounts.AccountsView.HandleAsync(StatusBarMessage message) in C:\Dev\cds-order-entry.visualstudio.com\CDSBlazor\CDS\CDS\Pages\Accounts\AccountsView.razor.cs:line 43
at CDS.Core.EventAggregator.EventAggregator.PublishAsync(Object message) in C:\Dev\cds-order-entry.visualstudio.com\CDSBlazor\CDS\CDS\Core\EventAggregator\EventAggregator.cs:line 78
at CDS.Pages.Accounts.Search.SearchView.ShowMessage(String message) in C:\Dev\cds-order-entry.visualstudio.com\CDSBlazor\CDS\CDS\Pages\Accounts\Search\SearchView.razor.cs:line 1916
at CDS.Pages.Accounts.Search.SearchView.DoSearch() in C:\Dev\cds-order-entry.visualstudio.com\CDSBlazor\CDS\CDS\Pages\Accounts\Search\SearchView.razor.cs:line 1397
at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
at Syncfusion.Blazor.Internal.SfBaseUtils.InvokeEvent[T](Object eventFn, T eventArgs)
at Syncfusion.Blazor.Buttons.SfButton.OnClickHandler(MouseEventArgs args)
at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)
Note that this exception only occurred when the app was published to IIS. When running in VS, the exception is not thrown.
(Note: This will be self-answered. It took me much time to debug this, and I want to make it easier for the next guy.)
The fix is actually very easy: Just wrap it in InvokeAysnc like this:
InvokeAsync(() =>
{
this.ToastObj.Show(model);
});
This method is ONLY available on the ComponentBase call, meaning it must be called from the codebehind of the page.
The exception message actually says exactly what the issue is: Use InvokeAsync() to switch execution to the Dispatcher when triggering rendering or component state.
The moral of the story is that if you are in an async method, wrap any rendering or component state change in InvokeAsync to ensure it is executed on the UI thread, not a background thread.

ServiceFabric: Service does not exist during deployment

I have an existing system using service fabric. Everything is fine except during a service publish the service is unavailable and any resolutions return an error.
This is expected however it would be nice if during this time instead the calls just waited or timedout. During this time my error logs will sometimes fill up with 200K lines of the same error.
I want some code like the following however where would it go?
public async Task Execute(Func<Task> action)
{
try
{
action()
.ConfigureAwait(false);
}
catch (FabricServiceNotFoundException ex)
{
await Task.Delay(TimeSpan.FromSeconds(??))
.ConfigureAwait(false);
action()
.ConfigureAwait(false);
}
}
Error:
System.Fabric.FabricServiceNotFoundException: Service does not exist. ---> System.Runtime.InteropServices.COMException: Exception from HRESULT: 0x80071BCD
at System.Fabric.Interop.NativeClient.IFabricServiceManagementClient6.EndResolveServicePartition(IFabricAsyncOperationContext context)
at System.Fabric.FabricClient.ServiceManagementClient.ResolveServicePartitionEndWrapper(IFabricAsyncOperationContext context)
at System.Fabric.Interop.AsyncCallOutAdapter2`1.Finish(IFabricAsyncOperationContext context, Boolean expectedCompletedSynchronously)
--- End of inner exception stack trace ---
at Microsoft.ServiceFabric.Services.Client.ServicePartitionResolver.ResolveHelperAsync(Func`5 resolveFunc, ResolvedServicePartition previousRsp, TimeSpan resolveTimeout, TimeSpan maxRetryInterval, CancellationToken cancellationToken, Uri serviceUri)
at Microsoft.ServiceFabric.Services.Communication.Client.CommunicationClientFactoryBase`1.CreateClientWithRetriesAsync(ResolvedServicePartition previousRsp, TargetReplicaSelector targetReplicaSelector, String listenerName, OperationRetrySettings retrySettings, Boolean doInitialResolve, CancellationToken cancellationToken)
at Microsoft.ServiceFabric.Services.Communication.Client.CommunicationClientFactoryBase`1.GetClientAsync(ResolvedServicePartition previousRsp, TargetReplicaSelector targetReplica, String listenerName, OperationRetrySettings retrySettings, CancellationToken cancellationToken)
at Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client.FabricTransportServiceRemotingClientFactory.GetClientAsync(ResolvedServicePartition previousRsp, TargetReplicaSelector targetReplicaSelector, String listenerName, OperationRetrySettings retrySettings, CancellationToken cancellationToken)
at Microsoft.ServiceFabric.Services.Communication.Client.ServicePartitionClient`1.GetCommunicationClientAsync(CancellationToken cancellationToken)
at Microsoft.ServiceFabric.Services.Communication.Client.ServicePartitionClient`1.InvokeWithRetryAsync[TResult](Func`2 func, CancellationToken cancellationToken, Type[] doNotRetryExceptionTypes)
at Microsoft.ServiceFabric.Services.Remoting.V2.Client.ServiceRemotingPartitionClient.InvokeAsync(IServiceRemotingRequestMessage remotingRequestMessage, String methodName, CancellationToken cancellationToken)
at Microsoft.ServiceFabric.Services.Remoting.Builder.ProxyBase.InvokeAsyncV2(Int32 interfaceId, Int32 methodId, String methodName, IServiceRemotingRequestMessageBody requestMsgBodyValue, CancellationToken cancellationToken)
at Microsoft.ServiceFabric.Services.Remoting.Builder.ProxyBase.ContinueWithResultV2[TRetval](Int32 interfaceId, Int32 methodId, Task`1 task)
As expected, Service Fabric have to shutdown the service to start the new version, this will cause a transient error like the one you've got.
By default, the Remoting APIs already have a retry logic built-in, from the docs:
The service proxy handles all failover exceptions for the service
partition it is created for. It re-resolves the endpoints if there are
failover exceptions (non-transient exceptions) and retries the call
with the correct endpoint. The number of retries for failover
exceptions is indefinite. If transient exceptions occur, the proxy
retries the call.
With that said, you should not require to add extra retry logic, maybe you should try adjust the OperationRetrySettings for a better handling of these retries.
If does not solve the problem, and you still want to add the logic in your code, the simplest way to handle it is using a transient-fault-handling library like Polly, something like below:
var policy = Policy
.Handle<FabricServiceNotFoundException>()
.WaitAndRetry(new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
});
policy.Execute(() => DoSomething());
In this sample, you do an exponential backoff between retries, if the number of calls is too big, I would recomend implement the circuit breaker approach instead.

NServiceBus Send() vs SendLocal() and exceptions

We are implementing a saga that calls out to other services with NServiceBus. I'm not quite clear about how NServiceBus deals with exceptions inside a saga.
Inside the saga we have a handler, and that handler calls an external service that should only be called once the original message handler completes succesfully. Is it okay to do:
public void Handle(IFooMessage message)
{
var message = Bus.CreateInstance<ExternalService.IBarMessage>();
Bus.Send(message);
// something bad happens here, exception is thrown
}
or will the message be sent to ExternalService multiple times? Someone here has suggested changing it to:
// handler in the saga
public void Handle(IFooMessage message)
{
// Do something
var message = Bus.CreateInstance<ISendBarMessage>();
Bus.SendLocal(message);
// something bad happens, exception is thrown
}
// a service-level handler
public void Handle(ISendBarMessage message)
{
var message = Bus.CreateInstance<ExternalService.IBarMessage>();
Bus.Send(message);
}
I've done an experiment and from what I can tell the first method seems fine, but I can't find any documentation other than http://docs.particular.net/nservicebus/errors/ which says:
When an exception bubbles through to the NServiceBus infrastructure, it rolls back the transaction on a transactional endpoint, causing the message to be returned to the queue, and any messages that user code tried to send or publish to be undone as well.
Any help to clarify this point would be much appreciated.
As long as you're doing messaging from your saga and not doing any web service calls, then you're safe - no need to do SendLocal.

NServiceBus HandleMessage async causes crash

I'm looking at NServiceBus v3.3.0.0, in our MessageHandler it calls an external WCF service.
If the WCF service is called synchronously and that service throws an exception, NServiceBus handles it perfectly and retries the process as per configuration.
But, if the WCF service is called asynchronously and an exception is thrown then the subscriber process crashes.
So, for example, this handles the exception fine if service.Update throws
public class LeagueMessageHandler : IHandleMessages<LeagueMessage>
{
public void Handle(LeagueMessage message)
{
var service = new LeagueService.LeagueContractClient();
var league = service.Update(leagueDto);
}
}
but if the call to service.UpdateAsync throws an exception then the process crashes
public class LeagueMessageHandler : IHandleMessages<LeagueMessage>
{
public async void Handle(LeagueMessage message)
{
var service = new LeagueService.LeagueContractClient();
var league = await service.UpdateAsync(leagueDto);
}
}
The WCF service is just added as a Service Reference to the class library, it generates the Async method wrappers.
Edit after Adam and Udi comments.
It looks like the issue is unrelated to NServiceBus it's more to do with how console applications handle async methods throwing exceptions. Please see thread
Catch unhandled exceptions from async
Stephen Cleary has written this
http://nuget.org/packages/Nito.AsyncEx
which helps you roll your own SynchronisationContext which handles catching the exception. So the WCF call above is wrapped such...
var league = AsyncContext.Run(() => service.UpdateAsync(leagueDto));
when the exception gets thrown it is caught within that context and the console app no longer closes.
When you call it async, the exception happens on a different thread than the one that is processing the message. For that reason, there's no way for NServiceBus to know which message was the one to cause that exception, so it can't roll anything back.
When using NServiceBus, your overall architecture is already asynchronous - there really isn't any need to perform these WCF calls asynchronously.

Is there a way to get error feedback on asynchronous WCF calls?

I have a WCF service which works 100% in the synchronous (blocking) mode and I now need to rework a call so that it uses the async pattern.
The service uses authentication and performs a chunked file transfer from client to server so I have reworked it to use the 'Begin' async prefix to kick off the call.
Now I'm testing for errors by deliberately mangling the user credentials which causes the call to timeout on each part of the file chunk it tries to transfer, which takes ages. The problem is that I don't get any error feedback and can't see how to get any if the async call fails. This leads to some very large files failing to upload at all, but the client being unaware of it as no exceptions are thrown.
I have the Debug->Exceptions->All CLR exceptions ticked to see if there are any exceptions being swallowed but still nothing.
So in summary, how do you get error feedback from async calls in WCF?
Thanks in advance,
Ryan
The server caches the exception for you and if you call the end operation completion method for your async call it will throw any exceptions that occured.
private void go_Click(object sender, EventArgs e)
{
client.BeginDoMyStuff(myValue, new AsyncCallback(OnEndDoMyStuff), null);
}
public void OnEndDoMyStuff(IAsyncResult asyncResult)
{
this.Invoke(new MethodInvoker(delegate() {
// This will throw if we have had an error
client.EndDoMyStuff(asyncResult);
}));
}