C# ASP.NET Core Fire and forget - c#

I have a sync controller method
public IActionResult Action(int num)
{
//operations-1
Task.Run(() => DoWork(num));
//operations-2
int a = num + 123;
return Ok(a);
}
and DoWork method
private bool DoWork(int num)
{
//operations
return true;
}
What I'm trying to do is to run DoWork method in background when calling that endpoint from Postman, but I want to get result in Postman and then debug DoWork method (from breakpoint in DoWork method) - is it possible?
For that moment, controller action and DoWork() are executing simultaneously but when I reach
return Ok(a);
applications waits for DoWork instead of returning value. I have tried also
Task.Factory.StartNew(() => DoWork());
ThreadPool.QueueUserWorkItem(o => DoWork());
but result is the same.
I want DoWork method to return value but that value is not neccessary by controller action method - it will be used in different place, not connected with that.

Use a background queue sometimes is overkill.
There are a number of sites showing a way to do when you need to access the database context. The problem of Task.Run in a controller, is that the spawned task cannot access the same context as the controller uses as it may (probably will) get disposed before that Task accesses it.
You can get round this by ensuring that the sub task only references Dependencies it knows will stay alive,
either by using a
singleton service or better for database context, using the IServiceScopeFactory .
The crux of this is to create a seperate dependency that can handle your database context or Repository. This can be injected into your controller as normal.
public void Execute(Func<IRepository, Task> databaseWork)
{
// Fire off the task, but don't await the result
Task.Run(async () =>
{
// Exceptions must be caught
try
{
using var scope = _serviceScopeFactory.CreateScope();
var repository = scope.ServiceProvider.GetRequiredService<IRepository>();
await databaseWork(repository);
}
catch (Exception e)
{
Console.WriteLine(e);
}
});
}
Then call this from your controller such as
// Delegate the slow task another task on the threadpool
_fireForgetRepositoryHandler.Execute(async repository =>
{
// Will receive its own scoped repository on the executing task
await repository.DoLOngRunningTaskAsync();;
});

Note: This builds upon Adem Catamak's answer.
Hangfire can be used, but no actual database is required because it can work with memory storage:
services.AddHangfire(opt => opt.UseMemoryStorage());
JobStorage.Current = new MemoryStorage();
While it has some overhead, Hangfire allows managing these jobs as opposed to having stuff running async and requiring custom code for simple things like run time, unhandled exceptions, custom code for DI support.
Credit: Codidact

Tasks are high level threads that make sure you are not blocking any context.
You either want to use something like RabbitMQ or IHostedService from ASP.NET Core 2.0 to do fire and forget task that kick in after a request has completed.

If you use Db in project, you can use Hangfire It is easy to use background process manager. https://www.hangfire.io/
you can use it very easyly like BackgroundJob.Enqueue(() => DoWork(num));

Related

can I update one entity in parallel threads c#

using: Asp.net Core, Entityframework Core, ABP 4.5
I have a user registration and initialization flow. But it takes a long time. I want to parallelize this. This is due to updating from the same entity, but with a different field.
My goal:
1. The endpoint should respond as soon as possible;
2. Long initialization is processed in the background;
Code-before (minor details omitted for brevity)
public async Task<ResponceDto> Rgistration(RegModel input)
{
var user = await _userRegistrationManager.RegisterAsync(input.EmailAddress, input.Password, false );
var result = await _userManager.AddToRoleAsync(user, defaultRoleName);
user.Code = GenerateCode();
await SendEmail(user.EmailAddress, user.Code);
await AddSubEntities(user);
await AddSubCollectionEntities(user);
await CurrentUnitOfWork.SaveChangesAsync();
return user.MapTo<ResponceDto>();
}
private async Task AddSubEntities(User user)
{
var newSubEntity = new newSubEntity { User = user, UserId = user.Id };
await _subEntityRepo.InsertAsync(newSubEntity);
//few another One-to-One entities...
}
private async Task AddSubEntities(User user)
{
List<AnotherEntity> collection = GetSomeCollection(user.Type);
await _anotherEntitieRepo.GetDbContext().AddRangeAsync(collection);
//few another One-to-Many collections...
}
Try change:
public async Task<ResponceDto> Rgistration(RegModel input)
{
var user = await _userRegistrationManager.RegisterAsync(input.EmailAddress, input.Password, false );
Task.Run(async () => {
var result = await _userManager.AddToRoleAsync(user, defaultRoleName);
});
Task.Run(async () => {
user.Code = GenerateCode();
await SendEmail(user.EmailAddress, user.Code);
});
Task.Run(async () => {
using (var unitOfWork = UnitOfWorkManager.Begin())
{//long operation. defalt unitOfWork out of scope
try
{
await AddSubEntities(user);
}
finally
{
unitOfWork.Complete();
}
}
});
Task.Run(async () => {
using (var unitOfWork = UnitOfWorkManager.Begin())
{
try
{
await AddSubCollectionEntities(user);
}
finally
{
unitOfWork.Complete();
}
}
});
await CurrentUnitOfWork.SaveChangesAsync();
return user.MapTo<ResponceDto>();
}
Errors:
here I get a lot of different errors related to competition. frequent:
A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext.
In few registratin calls: Cannot insert duplicate key row in object 'XXX' with unique index 'YYY'. The duplicate key value is (70). The statement has been terminated.
I thought on the server every request in its stream, but apparently not.
or all users are successfully registered, but they don’t have some sub-entity in the database. it’s much easier not to register the user than to figure out where he was initialized incorrectly =(
how to keep the user entity “open” for updating and at the same time “closed” for changes initiated by other requests? How to make this code thread safe and fast, can anyone help with advice?
Using Task.Run in ASP.NET is rarely a good idea.
Async methods run on the thread pool anyway, so wrapping them in Task.Run is simply adding overhead without any benefit.
The purpose of using async in ASP.NET is simply to prevent threads being blocked so they are able to serve other HTTP requests.
Ultimately, your database is the bottleneck; if all these operations need to happen before you return a response to the client, then there's not much you can do other than to let them happen.
If it is possible to return early and allow some operations to continue running on the background, then there are details here showing how that can be done.
Task.Run is not the same as parallel. It takes a new thread from the pool and runs the work on that thread, and since you're not awaiting it, the rest of the code can move on. However, that's because you're essentially orphaning that thread. When the action returns, all the scoped services will be disposed, which includes things like your context. Any threads that haven't finished, yet, will error out as a result.
The thread pool is a limited resource, and within the context of a web application, it equates directly to the throughput of your server. Every thread you take is one less request you can service. As a result, you're more likely to end up queuing requests, which will only add to processing time. It's virtually never appropriate to use Task.Run in a web environment.
Also, EF Core (or old EF, for that matter) does not support parallelization. So, even without the other problems described above, it will stop you cold from doing what you're trying to do here, regardless.
The queries you have here are not complex. Even if you were trying to insert 100s of things at once, it should still take only milliseconds to complete. If there is any significant delay here, you need to look at the resources of your database server and your network latency, first.
More likely than not, the slow-down is coming from the sending of the email. That too can likely be optimized, though. I was in a situation once where it was taking emails 30 seconds to send, until I finally figured out that it was an issue with our Exchange server, where an IT admin had idiotically introduced a 30 second delay on purpose. Regardless, it is generally always preferable to background things like sending emails, since they aren't core to your app's functionality. However, that means actually processing them in background, i.e. queue them and process them via something like a hosted service or an entirely different worker process.

KeyVaultClient hangs at GetSecretAsync [duplicate]

I have a multi-tier .Net 4.5 application calling a method using C#'s new async and await keywords that just hangs and I can't see why.
At the bottom I have an async method that extents our database utility OurDBConn (basically a wrapper for the underlying DBConnection and DBCommand objects):
public static async Task<T> ExecuteAsync<T>(this OurDBConn dataSource, Func<OurDBConn, T> function)
{
string connectionString = dataSource.ConnectionString;
// Start the SQL and pass back to the caller until finished
T result = await Task.Run(
() =>
{
// Copy the SQL connection so that we don't get two commands running at the same time on the same open connection
using (var ds = new OurDBConn(connectionString))
{
return function(ds);
}
});
return result;
}
Then I have a mid level async method that calls this to get some slow running totals:
public static async Task<ResultClass> GetTotalAsync( ... )
{
var result = await this.DBConnection.ExecuteAsync<ResultClass>(
ds => ds.Execute("select slow running data into result"));
return result;
}
Finally I have a UI method (an MVC action) that runs synchronously:
Task<ResultClass> asyncTask = midLevelClass.GetTotalAsync(...);
// do other stuff that takes a few seconds
ResultClass slowTotal = asyncTask.Result;
The problem is that it hangs on that last line forever. It does the same thing if I call asyncTask.Wait(). If I run the slow SQL method directly it takes about 4 seconds.
The behaviour I'm expecting is that when it gets to asyncTask.Result, if it's not finished it should wait until it is, and once it is it should return the result.
If I step through with a debugger the SQL statement completes and the lambda function finishes, but the return result; line of GetTotalAsync is never reached.
Any idea what I'm doing wrong?
Any suggestions to where I need to investigate in order to fix this?
Could this be a deadlock somewhere, and if so is there any direct way to find it?
Yep, that's a deadlock all right. And a common mistake with the TPL, so don't feel bad.
When you write await foo, the runtime, by default, schedules the continuation of the function on the same SynchronizationContext that the method started on. In English, let's say you called your ExecuteAsync from the UI thread. Your query runs on the threadpool thread (because you called Task.Run), but you then await the result. This means that the runtime will schedule your "return result;" line to run back on the UI thread, rather than scheduling it back to the threadpool.
So how does this deadlock? Imagine you just have this code:
var task = dataSource.ExecuteAsync(_ => 42);
var result = task.Result;
So the first line kicks off the asynchronous work. The second line then blocks the UI thread. So when the runtime wants to run the "return result" line back on the UI thread, it can't do that until the Result completes. But of course, the Result can't be given until the return happens. Deadlock.
This illustrates a key rule of using the TPL: when you use .Result on a UI thread (or some other fancy sync context), you must be careful to ensure that nothing that Task is dependent upon is scheduled to the UI thread. Or else evilness happens.
So what do you do? Option #1 is use await everywhere, but as you said that's already not an option. Second option which is available for you is to simply stop using await. You can rewrite your two functions to:
public static Task<T> ExecuteAsync<T>(this OurDBConn dataSource, Func<OurDBConn, T> function)
{
string connectionString = dataSource.ConnectionString;
// Start the SQL and pass back to the caller until finished
return Task.Run(
() =>
{
// Copy the SQL connection so that we don't get two commands running at the same time on the same open connection
using (var ds = new OurDBConn(connectionString))
{
return function(ds);
}
});
}
public static Task<ResultClass> GetTotalAsync( ... )
{
return this.DBConnection.ExecuteAsync<ResultClass>(
ds => ds.Execute("select slow running data into result"));
}
What's the difference? There's now no awaiting anywhere, so nothing being implicitly scheduled to the UI thread. For simple methods like these that have a single return, there's no point in doing an "var result = await...; return result" pattern; just remove the async modifier and pass the task object around directly. It's less overhead, if nothing else.
Option #3 is to specify that you don't want your awaits to schedule back to the UI thread, but just schedule to the thread pool. You do this with the ConfigureAwait method, like so:
public static async Task<ResultClass> GetTotalAsync( ... )
{
var resultTask = this.DBConnection.ExecuteAsync<ResultClass>(
ds => return ds.Execute("select slow running data into result");
return await resultTask.ConfigureAwait(false);
}
Awaiting a task normally would schedule to the UI thread if you're on it; awaiting the result of ContinueAwait will ignore whatever context you are on, and always schedule to the threadpool. The downside of this is you have to sprinkle this everywhere in all functions your .Result depends on, because any missed .ConfigureAwait might be the cause of another deadlock.
This is the classic mixed-async deadlock scenario, as I describe on my blog. Jason described it well: by default, a "context" is saved at every await and used to continue the async method. This "context" is the current SynchronizationContext unless it it null, in which case it is the current TaskScheduler. When the async method attempts to continue, it first re-enters the captured "context" (in this case, an ASP.NET SynchronizationContext). The ASP.NET SynchronizationContext only permits one thread in the context at a time, and there is already a thread in the context - the thread blocked on Task.Result.
There are two guidelines that will avoid this deadlock:
Use async all the way down. You mention that you "can't" do this, but I'm not sure why not. ASP.NET MVC on .NET 4.5 can certainly support async actions, and it's not a difficult change to make.
Use ConfigureAwait(continueOnCapturedContext: false) as much as possible. This overrides the default behavior of resuming on the captured context.
I was in the same deadlock situation but in my case calling an async method from a sync method, what works for me was:
private static SiteMetadataCacheItem GetCachedItem()
{
TenantService TS = new TenantService(); // my service datacontext
var CachedItem = Task.Run(async ()=>
await TS.GetTenantDataAsync(TenantIdValue)
).Result; // dont deadlock anymore
}
is this a good approach, any idea?
Just to add to the accepted answer (not enough rep to comment), I had this issue arise when blocking using task.Result, event though every await below it had ConfigureAwait(false), as in this example:
public Foo GetFooSynchronous()
{
var foo = new Foo();
foo.Info = GetInfoAsync.Result; // often deadlocks in ASP.NET
return foo;
}
private async Task<string> GetInfoAsync()
{
return await ExternalLibraryStringAsync().ConfigureAwait(false);
}
The issue actually lay with the external library code. The async library method tried to continue in the calling sync context, no matter how I configured the await, leading to deadlock.
Thus, the answer was to roll my own version of the external library code ExternalLibraryStringAsync, so that it would have the desired continuation properties.
wrong answer for historical purposes
After much pain and anguish, I found the solution buried in this blog post (Ctrl-f for 'deadlock'). It revolves around using task.ContinueWith, instead of the bare task.Result.
Previously deadlocking example:
public Foo GetFooSynchronous()
{
var foo = new Foo();
foo.Info = GetInfoAsync.Result; // often deadlocks in ASP.NET
return foo;
}
private async Task<string> GetInfoAsync()
{
return await ExternalLibraryStringAsync().ConfigureAwait(false);
}
Avoid the deadlock like this:
public Foo GetFooSynchronous
{
var foo = new Foo();
GetInfoAsync() // ContinueWith doesn't run until the task is complete
.ContinueWith(task => foo.Info = task.Result);
return foo;
}
private async Task<string> GetInfoAsync
{
return await ExternalLibraryStringAsync().ConfigureAwait(false);
}
quick answer :
change this line
ResultClass slowTotal = asyncTask.Result;
to
ResultClass slowTotal = await asyncTask;
why? you should not use .result to get the result of tasks inside most applications except console applications if you do so your program will hang when it gets there
you can also try the below code if you want to use .Result
ResultClass slowTotal = Task.Run(async ()=>await asyncTask).Result;

Implementing SendBatchAsync for Microsoft Azure Service Bus Brokered Messages

My requirement is that I have to send a list of brokered messages to the azure service bus asynchronously. However I am not able to implement the SendBatchAsync method properly. Let me explain in detail. Here is my code:
public async Task SendBatchEnrollmentMessages()
{
while(somevalue)
{
//logic to fetch data from the SQL db through stored proc into list of brokered message i.e. messageList
if(messageList.Count() > 0)
{
await sender.SendMessagesAsync(messageList);
}
//some more logic for somevalue
}
}
where the SendMessageAsync logic is :
public async Task SendMessagesAsync(List<BrokeredMessage> brokeredMessageList)
{
var topicClient = CreateTopicClient();
await topicClient.SendBatchAsync(brokeredMessageList);
}
My issue is that when I debug the application using break point, the compiler comes till await topicClient.SendBatchAsync(brokeredMessageList); and exits the code i.e application debuggin is completed. It doesn't return back to the while condition. However instead of using SendBatchAsync if I use SendBatch, it works fine. What am I doing wrong?
Solution: The issue was with the test method which was calling the above funciton. It was of type void. It should have been of type async Task. A big thanks to Ned Stoyanov for helping me out in this.
An async method returns when after encounters an await statement and sets up the asynchronous operation being awaited. The rest of the method then continues after the await is finished.
You probably can't step through async methods like that, but try putting a breakpoint after the await and it should get hit when the asynchronous call completes.
Alternatively you may have a deadlock, see this post for some ways to avoid it.
As mentioned in our discussion the unit test needs to be async as well, return a Task and await any async calls
[TestMethod]
public async Task SendRegionsEnrollmentMessages()
{
EventManager eventMgr = new EventManager(clientUrn, programUrn, "CW");
await eventMgr.SendBatchEvents(EventType.ENROLLMENT);
}

Calling Task-based methods from ASMX

I have a recent experience I'd like to share that may be helpful to anyone having to maintain a legacy ASMX web service that must be updated to call Task-based methods.
I've recently been updating an ASP.NET 2.0 project that includes a legacy ASMX web service to ASP.NET 4.5. As part of the update, I introduced a Web API interface to allow advanced automation of the application. The ASMX service has to co-exist with the new API for backwards-compatibility.
One of the functions of the application is to be able to request data from external data sources (industrial plant historians, bespoke web services, etc.) on behalf of the caller. As part of the upgrade, I re-wrote substantial parts of the data access layer to asynchronously request data using a Task-based asynchronous pattern. Given that it's not possible to use aync/await in an ASMX service, I modified the ASMX methods to make blocking calls to the asynchronous methods i.e. calling the Task-based method and then using Task.WaitAll to block the thread until the Task completes.
When calling any ASMX method that was calling a method returning Task or Task<T> under the hood, I found that the request always timed out. When I stepped through the code, I could see that that the asynchronous code was successfully executing, but the call to Task.WaitAll never detected that the task had completed.
This caused a major headache: how could the ASMX service happily co-exist with the new asynchronous data access capabilities?
I've recently been updating an ASP.NET 2.0 project that includes a legacy ASMX web service to ASP.NET 4.5.
The first thing to do is ensure that httpRuntime#targetFramework is set to 4.5 in your web.config.
the parent task (i.e. the method call in the ASMX that returned a Task) was never detected as completing.
This is actually a classic deadlock situation. I describe it in full on my blog, but the gist of it is that await will (by default) capture a "context" and use that to resume the async method. In this case, that "context" is an ASP.NET request context, which only allows one thread at a time. So, when the asmx code further up the stack blocks on the task (via WaitAll), it is blocking a thread in that request context, and the async method cannot complete.
Pushing the blocking wait to a background thread would "work", but as you note it is a bit brute-force. A minor improvement would be to just use var result = Task.Run(() => MethodAsync()).Result;, which queues the background work to the thread pool and then blocks the request thread waiting for it to complete. Alternatively, you may have the option of using ConfigureAwait(false) for every await, which overrides the default "context" behavior and allows the async method to continue on a thread pool thread outside the request context.
But a much better improvement would be to use asynchronous calls "all the way". (Side note: I describe this in more detail in an MSDN article on async best practices).
ASMX does allow asynchronous implementations of the APM variety. I recommend that you first make your asmx implementation code as asynchronous as possible (i.e., using await WhenAll rather than WaitAll). You'll end up with a "core" method that you then need to wrap in an APM API.
The wrapper would look something like this:
// Core async method containing all logic.
private Task<string> FooAsync(int arg);
// Original (synchronous) method looked like this:
// [WebMethod]
// public string Foo(int arg);
[WebMethod]
public IAsyncResult BeginFoo(int arg, AsyncCallback callback, object state)
{
var tcs = new TaskCompletionSource<string>(state);
var task = FooAsync(arg);
task.ContinueWith(t =>
{
if (t.IsFaulted)
tcs.TrySetException(t.Exception.InnerExceptions);
else if (t.IsCanceled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(t.Result);
if (callback != null)
callback(tcs.Task);
});
return tcs.Task;
}
[WebMethod]
public string EndFoo(IAsyncResult result)
{
return ((Task<string>)result).GetAwaiter().GetResult();
}
This gets a bit tedious if you have a lot of methods to wrap, so I wrote some ToBegin and ToEnd methods as part of my AsyncEx library. Using these methods (or your own copy of them if you don't want the library dependency), the wrappers simplify nicely:
[WebMethod]
public IAsyncResult BeginFoo(int arg, AsyncCallback callback, object state)
{
return AsyncFactory<string>.ToBegin(FooAsync(arg), callback, state);
}
[WebMethod]
public string EndFoo(IAsyncResult result)
{
return AsyncFactory<string>.ToEnd(result);
}
Upon further investigation, I discovered that sub-tasks created by the initial task could be awaited without any problems, but the parent task (i.e. the method call in the ASMX that returned a Task<T>) was never detected as completing.
The investigation led me to theorise that there was some sort of incompatibility between the legacy Web Services stack and the Task Parallel Library. The solution that I came up with involves creating a new thread to run the Task-based method calls, the idea being that a separate thread would not be subject to thread/task management incompatibilities that existed in the thread processing the ASMX request. To this end I created a simple helper class that will run a Func<T> in a new thread, block the current thread until the new thread terminates and then returns the result of the function call:
public class ThreadRunner<T> {
// The function result
private T result;
//The function to run.
private readonly Func<T> function;
// Sync lock.
private readonly object _lock = new object();
// Creates a new ThreadRunner<T>.
public ThreadRunner(Func<T> function) {
if (function == null) {
throw new ArgumentException("Function cannot be null.", "function");
}
this.function = function;
}
// Runs the ThreadRunner<T>'s function on a new thread and returns the result.
public T Run() {
lock (_lock) {
var thread = new Thread(() => {
result = function();
});
thread.Start();
thread.Join();
return result;
}
}
}
// Example:
//
// Task<string> MyTaskBasedMethod() { ... }
//
// ...
//
// var tr = new ThreadRunner<string>(() => MyTaskBasedMethod().Result);
// return tr.Run();
Running the Task-based method in this way works perfectly and allows the ASMX call to complete successfully, but it's obviously a bit brute-force to spawn a new thread for every asynchronous call; alternatives, improvements or suggestions are welcome!
This may be an old topic but it contains the best answer I have been able to find to help maintain legacy code using ASMX and WebMethod to call newer async functions synchronously.
I am new to contributing to stackoverflow so I don't have the reputation to post a comment to Graham Watts solution. I shouldn't really be responding to another answer - but what other choice do I have.
Graham's answer has proved to be a good solution for me. I have a legacy application that is used internally. Part of it called an external API which has since been replaced. To use the replacement the legacy app was upgraded to .NET 4.7 as the replacement uses Tasks extensively. I know the "right" thing to do would be to rewrite the legacy code but there is no time or budget for such an extensive exercise.
The only enhancement I had to make was to capture exceptions. This may not be the most elegant solution but it works for me.
public class ThreadRunner<T>
{
// Based on the answer by graham-watts to :
// https://stackoverflow.com/questions/24078621/calling-task-based-methods-from-asmx/24082534#24082534
// The function result
private T result;
//The function to run.
private readonly Func<T> function;
// Sync lock.
private readonly object _lock = new object();
// Creates a new ThreadRunner<T>.
public ThreadRunner(Func<T> function)
{
if (function == null)
{
throw new ArgumentException("Function cannot be null.", "function");
}
this.function = function;
}
Exception TheException = null;
// Runs the ThreadRunner<T>'s function on a new thread and returns the result.
public T Run()
{
lock (_lock)
{
var thread = new Thread(() => {
try
{
result = function();
}catch(Exception ex)
{
TheException = ex;
}
});
thread.Start();
thread.Join();
if (TheException != null)
throw TheException;
return result;
}
}
}
// Example:
//
// Task<string> MyTaskBasedMethod() { ... }
//
// ...
//
// var tr = new ThreadRunner<string>(() => MyTaskBasedMethod().Result);
// return tr.Run();

Nested Task - The current synchronizationcontext may not be used as a taskscheduler

I've got a WPF MVVM application which has the concept of "services".
Each service method gets passed a callback as on of the parameters, it creates the Task, does the work and then calls the callback in the ViewModel in the continuation.
In one of my service methods the "work" involves synchronising databases and then loading the updated database from the DB. Another method counts the number of records in a table. Here are some simplified examples.
public void GetStudents(Action<IEnumerable<Student>> callback)
{
Task.Run(() =>
{
Sync();
return students;
}).ContinueWith(task =>
{
callback(task.Result);
}, TaskScheduler.FromCurrentSynchronizationContext());
}
public void CountStudents(Action<int> callback)
{
Task.Run(() =>
{
return students.Count();
}).ContinueWith(task =>
{
callback(task.Result);
}, TaskScheduler.FromCurrentSynchronizationContext());
}
These methods were called separately from different ViewModels and they worked as expected; however (isn't there always a however) I've added an event on synchronisation completed using the Prism EventAggregator. When sync has completed, if the Student table has been update I want to re-run the count method. The problem is because this is happening from the context of the other Task I'm getting this exception.
"The current synchronizationcontext may not be used as a taskscheduler"
I've tried joining the Task to the parent task with TaskCreationOptions.AttachedToParent but that doesn't fix the issue.
The more I think about it I think I should move the Task creation to my ViewModels, it seems the most logical and correct place for it to be, but is there something I'm missing that means I don't have to do that in this instance?

Categories

Resources