Awaitable task in not awaited task - c#

I'm working on a little console application that selects messages from a database queue
and forwards the messages to a rest api (ASP.NET Web Api).
In general the application does the following steps:
Get the number of pending messages
Load the last pending messages
Post the message to the rest api
Remove the message from the database
To make the program more flexible and to have the ability to process every single message
in a separate database transcation the steps 2 - 3 will be executed as tasks.
This means if there are four messages in the database, we'll have four tasks that
should run nearly parallel and process the messages.
This is what the code looks like:
Database message
public class DatabaseMessage
{
public string Message { get; set; }
}
UnitOfWork (Interface IUnitOfWork)
public class UnitOfWork
{
// ... extermely simplified
public int GetNumberOfPendingMessages() { ... }
public DatabaseMessage GetNextPendingMessage() { ... }
public void DeleteMessage(DatabaseMessage message) { ... }
}
HttpService (Interface IHttpService)
public class HttpService
{
private readonly HttpClient _httpClient;
public HttpService()
{
_httpClient = new HttpClient();
/* Some initalization stuff for the HttpClient object */
}
public async Task<HttpResponse> PostMessage(DatabaseMessage message)
{
var content = /* Create content object */
return await _httpClient.PostAsync(..., content);
}
}
MessageProcessingService (Interface IMessageProcessingService)
public class MessageProcessingService
{
private readonly IHttpService _httpService;
private readonly Semaphore _databaseProcessingSemaphore;
public MessageProcessingService(IHttpService httpService)
{
_httpService = httpService;
}
public async Task ProcessDatabaseMessages()
{
var unitOfWork = new UnitOfWork();
var numberOfPendingMessages = unitOfWork.GetNumberOfPendingMessages();
var messageProcessingTasks = new List<Task>();
for(int t = 0; t < numberOfPendingMessages; t++)
{
messageProcessingTasks.Add(new Task(() => {
ProcessMessageAsTask();
}));
}
var continuationHandler = Task.WhenAll(messageProcessingTasks);
messageProcessingTasks.ForEach(e => e.Start());
await continuationHandler;
}
private void ProcessMessageAsTask()
{
// Single unit of work for each tasks
var unitOfWork = new UnitOfWork();
try{
// Starting a database transaction
unitOfWork.StartTransaction();
_databaseProcessingSemaphore.OnWait();
var message = unitOfWork.GetNextPendingMessage();
_databaseProcessingSemaphore.Release();
if(message != null)
{
var response = _httpService.PostMessage(message).Result;
if(response == HttpStatus.OK)
{
unitOfWork.DeleteMessage(message);
unitOfWork.Commit();
}
else
{
unitOfWork.Rollback();
}
}
else
{
unitOfWork.Commit();
}
}
catch(Exception ex)
{
unitOfWork.Rollback();
// Further error handling...
}
}
}
For better understanding, the HttpClient object is created and managed by Unity and is injected
into the MessageProcessingService object. The HttpClient is held as singleton in the container.
I'm facing now the problem that the call of the method _httpService.PostMessage(). For example, if there are five
messages in the message queue, the call fails five times with an exception that tells me that an task has been canceled.
My question is now what is the problem with PostAsync call of the .NET HttpClient? Is the issue caused by the .Result option or would
it be better to create a new instance of the HttpClient for each message processing task?
Or is there a general problem with the architecture with tasks and the processing of rest api calls?
Update 2018-04-04 - 08:09
I've now made the method ProcessMessageAsTask async and I'm awaiting now the call of the HttpService.
But now I don't get any exception at all. In the ressource monitor and while debugging I can see that all tasks reach the call of the HttpClient (return await _httpClient.PostAsync(..., content);)
But there is no exception nor will the messages be posted. But I don't get any exceptions. The program will be closed immediately after the calls of the HttpClient. All futher statements were not processed.
Changes:
public async Task ProcessDatabaseMessages()
{
var unitOfWork = new UnitOfWork();
var numberOfPendingMessages = unitOfWork.GetNumberOfPendingMessages();
var messageProcessingTasks = new List<Task>();
for(int t = 0; t < numberOfPendingMessages; t++)
{
messageProcessingTasks.Add(new Task(async () => {
await ProcessMessageAsTask();
}));
}
var continuationHandler = Task.WhenAll(messageProcessingTasks);
messageProcessingTasks.ForEach(e => e.Start());
await continuationHandler;
}
private async Task ProcessMessageAsTask()
{
// Single unit of work for each tasks
var unitOfWork = new UnitOfWork();
try{
// Starting a database transaction
unitOfWork.StartTransaction();
_databaseProcessingSemaphore.OnWait();
var message = unitOfWork.GetNextPendingMessage();
_databaseProcessingSemaphore.Release();
if(message != null)
{
var response = await _httpService.PostMessage(message);
if(response == HttpStatus.OK)
{
unitOfWork.DeleteMessage(message);
unitOfWork.Commit();
}
else
{
unitOfWork.Rollback();
}
}
else
{
unitOfWork.Commit();
}
}
catch(Exception ex)
{
unitOfWork.Rollback();
// Further error handling...
}
}

Related

How to access a Realm instance in the same thread that it was created in .NET (Xamarin-UWP)?

I am working on the migration from SQLite/WebApis to Realms in an App in Xamarin Forms.
This application manages a messaging service (Chat through SignalR).
The problem that has arisen is when a message is received, which I try to store in a database, the application crashes unexpectedly. (Question that did not happen with SQLite).
I am getting the famous error: realm accessed from incorrect, exactly when the 3rd or 4th message is trying to insert, it has been a bit difficult to see the precise moment.
As I could read, both in questions right here on Stackoverflow, and on the Dot-Net Realm GitHub. the instance must be accessed in the same thread that it was created.
As they suggest, one way to achieve this is by using the correct SynchronizationContext through AsyncContext.Run Nito.AsyncEx by Stephen Cleary or Device.BeginInvokeOnMainThread.
I ask the question because unfortunately, I handle the subject of threads and tasks at a basic level, in what way do I have to approach the previous suggestions to my code? or what should I set correctly to access the thread in which it was created?
This is my code:
LobbyViewModel
public LobbyViewModel()
{
ChatService.OnReceivePrivateMessage += ChatService_OnReceivePrivateMessage;
}
private async void ChatService_OnReceivePrivateMessage(object sender, MessageEventArgs args)
{
await SendMessage(…);
}
private async Task SendMessage(…)
{
await UpdateMetaData(…);
}
public async Task UpdateMetaData(…)
{
await ManagerService.InsertMetaDataChatAsync(metaDataChat);
}
ManagerService
public async Task InsertMetaDataChatAsync (MetaDataChatModel MetaDataChatModel)
{
IMetaDataChatServices MetaDataChatServices = App.AppContainer.Resolve<IMetaDataChatServices>();
IResponse DataResult = (await MetaDataChatServices.Insert(MetaDataChatModel));
}
Repository
public async Task<IResponse> CreateAsync(MetaDataChatModel MetaDataChatModel)
{
MetaDataChatModel.Id = Guid.NewGuid().ToString();
MetaDataChatModel.Partition = _realmManager.projectPartition;
var MetaDataChatDto = MetaDataChatModel.ToDto();
var realmInstance = await _realmManager.GetInstanceAsync();
if (realmInstance == null)
return new Response(false, "Realm instance can't be null");
await realmInstance.WriteAsync(realm =>
{
realm.Add(MetaDataChatDto, false);
});
return new Response(true);
}
RealmManager
public RealmManager()
{
RealmApp = App.Create(appId);
//DataBase = Realms.Realm.GetInstance(syncConfig);
}
public Task<Realms.Realm> GetInstanceAsync()
{
return Realms.Realm.GetInstanceAsync(syncConfig);
}
public async Task RealmLogInAsync()
{
if (RealmApp.CurrentUser == null)
{
User user = await RealmApp.
LogInAsync(Credentials.EmailPassword("email", "password"));
if (user == null)
{
return;
}
}
projectPartition = $"project={RealmApp.CurrentUser.Id}";
syncConfig = new SyncConfiguration(projectPartition, RealmApp.CurrentUser);
}
public async Task RealmLogOutAsync()
{
if (RealmApp != null && RealmApp.CurrentUser!=null)
{
await RealmApp.CurrentUser?.LogOutAsync();
}
}

TPL .Net Concurrenty Issue with Azure EventHub Producer

I am working with Azure Event Hub producer client and reading messages off of a kafka stream then pass it along to deserialize/map, then pass to Event Hub. I have the consume loop which is creating a task for each consume and then two methods to do processing(this seems to have greatly improved the speed from the kafka lag perspective. However, Event hub makes you create an event batch which I don't necessarily want to use. I just want to send the data one message at a time for now. In order to create a new batch I have to call Dispose(). I am running into an issue where there's another call to the function by the the time I call Dispose() and I get an error saying the object is being used by event hub.
I've also tried using the overload for eventHubProducerClient.SendAsync that allows you to pass in a IEnumerable but i'm running into the same issue with that.
So I believe this to be a synchronization issue, or maybe I need to do a lock somewhere?
Any help would be appreciated.
public void Execute()
{
using (_consumer)
{
try
{
_consumer.Subscribe(_streamConsumerSettings.Topic);
while (true)
{
var result = _consumer.Consume(1000);
if (result == null)
{
continue;
}
var process = Task.Factory.StartNew(() => ProcessMessage(result?.Message?.Value));
var send = process.ContinueWith(t => SendMessage(process.Result));
}
}
catch (ConsumeException e)
{
_logger.LogError(e, e.StackTrace ?? e.Message);
_cancelConsume = true;
_consumer.Close();
RestartConsumer();
}
}
}
public static EquipmentJson ProcessMessage(byte[] result)
{
var json = _messageProcessor.DeserializeAndMap(result);
return json;
}
public static void SendMessage(EquipmentJson message)
{
try
{
_eventHubClient.AddToBatch(message);
}
catch (Exception e)
{
_logger.LogError(e, e.StackTrace ?? e.Message);
}
}
public async Task AddToBatch(EquipmentJson message)
{
if
(!string.IsNullOrEmpty(message.EquipmentLocation))
{
try
{
var batch = await _equipmentLocClient.CreateBatchAsync();
batch.TryAdd(new EventData(Encoding.UTF8.GetBytes(message.EquipmentLocation)));
await _eventHubProducerClient.SendAsync(batch);
batch.Dispose();
_logger.LogInformation($"Data sent {DateTimeOffset.UtcNow}");
}
catch (Exception e)
{
_logger.LogError(e, e.StackTrace ?? e.Message);
}
}
}
public class EventHubClient : IEventHubClient
{
private readonly ILoggerAdapter<EventHubClient> _logger;
private readonly EventHubClientSettings _eventHubClientSettings;
private IMapper _mapper;
private static EventHubProducerClient _equipmentLocClient;
public EventHubClient(ILoggerAdapter<EventHubClient> logger, EventHubClientSettings eventHubClientSettings, IMapper mapper)
{
_logger = logger;
_eventHubClientSettings = eventHubClientSettings;
_mapper = mapper;
_equipmentLocClient = new EventHubProducerClient(_eventHubClientSettings.ConnectionString, _eventHubClientSettings.EquipmentLocation);
}
}
}
Based on my speculation in comments, I'm curious if refactoring to use async/await rather than the explicit continuation in the main loop may help. Perhaps something similar to the following LinqPad snippet:
async Task Main()
{
while (true)
{
var message = await Task.Factory.StartNew(() => GetText());
var events = new[] { new EventData(Encoding.UTF8.GetBytes(message)) };
await Send(events).ConfigureAwait(false);
}
}
public EventHubProducerClient client = new EventHubProducerClient("<< CONNECTION STRING >>");
public async Task Send(EventData[] events)
{
try
{
await client.SendAsync(events).ConfigureAwait(false);
"Sent".Dump();
}
catch (Exception ex)
{
ex.Dump();
}
}
public string GetText()
{
Thread.Sleep(250);
return "Test";
}
If you're set on keeping the continuation, I wonder if a slight structural refactoring in the continuation may help, both to push up creation of the events and to honor the await statements. Perhaps something similar to the following LinqPad snippet:
async Task Main()
{
while(true)
{
var t = Task.Factory.StartNew(() => GetText());
var _ = t.ContinueWith(async q =>
{
var events = new[] { new EventData(Encoding.UTF8.GetBytes(t.Result)) };
await Send(events).ConfigureAwait(false);
});
await Task.Yield();
}
}
public EventHubProducerClient client = new EventHubProducerClient("<< CONNECTION STRING >>");
public async Task Send(EventData[] events)
{
try
{
await client.SendAsync(events).ConfigureAwait(false);
"Sent".Dump();
}
catch (Exception ex)
{
ex.Dump();
}
}
public string GetText()
{
Thread.Sleep(250);
return "Test";
}

Custom producer consumer randomly stops while using blocking collection

I have following problem: Service is being run on the server. It has timer that ticks every 5 seconds. It is being modeled to produce items for a consumer. Items are being consumed in multi thread.
I've added logging but I cannot find out when and where any problem occurs and this just blocks. No exception or error. My goal is to keep getting requests to be processed from db and consume them. Timer is being a producer.
public class CustomProducerConsumer<T> : IDisposable
{
private readonly BlockingCollection<T> blockingCollection;
private readonly Action<T> consumeItem;
private readonly Task[] workers;
public CustomProducerConsumer(Action<T> consumeItem,
int degreeOfParallelism,
int capacity = 1024)
{
this.consumeItem = consumeItem;
this.blockingCollection = new BlockingCollection<T>(capacity);
this.workers = Enumerable.Range(1, degreeOfParallelism)
.Select(_ => Task.Factory.StartNew(Worker,
TaskCreationOptions.LongRunning))
.ToArray();
}
public void Dispose()
{
// Unblock all workers even if the client
// didn't call CompleteProcessing
if (!this.blockingCollection.IsAddingCompleted)
{
this.blockingCollection.CompleteAdding();
}
Task.WaitAll(this.workers);
this.blockingCollection.Dispose();
}
public void Process(T item)
{
this.blockingCollection.TryAdd(item);
}
private void Worker()
{
foreach (var item in this.blockingCollection.GetConsumingEnumerable())
{
this.consumeItem(item);
}
}
}
Here is my code from service:
private readonly BlockingCollection<StitchingRequestProcessingModel> requestsToBeProcessed =
new BlockingCollection<StitchingRequestProcessingModel>(10);
private readonly BlockingCollection<Dictionary<int, StitchingRequest[]>> pendingRequests =
new BlockingCollection<Dictionary<int, StitchingRequest[]>>(10);
private readonly Timer timer;
public Service()
{
InitializeComponent();
this.produceConsumer =
new CustomProducerConsumer<StitchingRequestModel>(this.ProcessItems,
Environment.ProcessorCount);
this.timer = new Timer(o =>
{
this.TimerElapsed();
this.timer.Change(TimeSpan.FromSeconds(5), Timeout.InfiniteTimeSpan);
}, null, TimeSpan.Zero, Timeout.InfiniteTimeSpan);
this.ConsumeRequests();
}
public void TimerElapsed()
{
try
{
//this just adds into the list an item to ping the db for pending requests when available
this.requestsToBeProcessed.Add(new StitchingRequestProcessingModel());
this.pendingRequests.TryTake(out Dictionary<int, Request[]> requests);
if (requests == null)
{
return;
}
foreach (KeyValuePair<int, Request[]> request in requests)
{
this.produceConsumer.Process(new StitchingRequestModel(request));
}
}
catch (Exception exception)
{
this.errorLogger.Error(exception.Message);
}
}
private void ConsumeRequests()
{
Task.Factory.StartNew(() =>
{
while (!this.requestsToBeProcessed.IsCompleted)
{
if (this.tokenSource.Token.IsCancellationRequested)
{
break;
}
StitchingRequestProcessingModel data = null;
try
{
data = this.requestsToBeProcessed.Take();
}
catch (InvalidOperationException)
{
}
if (data == null)
{
continue;
}
try
{
// this just executes sql query to get those request from db
var requests = this.requestService.GetPendingRequests();
this.pendingRequests.Add(requests);
}
catch (Exception exception)
{
this.errorLogger.Error(exception.Message, "Failed to get pending requests");
}
}
},
this.tokenSource.Token,
TaskCreationOptions.LongRunning, TaskScheduler.Current);
}
private void ProcessItems(StitchingRequestModel model)
{
foreach (StitchingRequest request in model.Requests)
{
this.requestsToBeProcessed.Add(new StitchingRequestProcessingModel(request);
}
}
Main reason why I placed consuming items into blocking collection is Nhibernate. It is giving me issues while doing multithreading. No ideas what else to try, nor why this approach is not working. I do not want to call CompleteAdding on blocking collection since I need requests to be added and just processed in first available thread.
Timer on each elapsed event will try to create an a pending request that will be added into blocking collection and processed on first available turn. Service runs for 2~3h and just stops. ProcessItems method can be long running. CPU is 8 core.
UPDATE
Added cancelation for consumer task.
Problem was solved with not working with entity object between consumer and producer. Created dto for info needed.

Queuing tasks in asp.net core

E.g. of functionality There is 20 users and they clicked send button almost in one time, so methods stacking in queue and first user message is sent and response received, after second third and so on. Users wont chat with other people but with device which response is pretty fast
So I am trying to queue Task which sends Message.
I found code samples that uses Task queuing as shown in Example 1 and Example 2.
Example 1
public class SerialQueue
{
readonly object _locker = new object();
WeakReference<Task> _lastTask;
public Task Enqueue(Action action)
{
return Enqueue<object>(() => {
action();
return null;
});
}
public Task<T> Enqueue<T>(Func<T> function)
{
lock (_locker)
{
Task lastTask = null;
Task<T> resultTask = null;
if (_lastTask != null && _lastTask.TryGetTarget(out lastTask))
{
resultTask = lastTask.ContinueWith(_ => function());
}
else
{
resultTask = Task.Run(function);
}
_lastTask = new WeakReference<Task>(resultTask);
return resultTask;
}
}
}
Example 2
public class TaskQueue
{
private readonly SemaphoreSlim _semaphoreSlim;
public TaskQueue()
{
_semaphoreSlim = new SemaphoreSlim(1);
}
public async Task<T> Enqueue<T>(Func<Task<T>> taskGenerator)
{
await _semaphoreSlim.WaitAsync();
try
{
return await taskGenerator();
}
finally
{
_semaphoreSlim.Release();
}
}
public async Task Enqueue(Func<Task> taskGenerator)
{
await _semaphoreSlim.WaitAsync();
try
{
await taskGenerator();
}
finally
{
_semaphoreSlim.Release();
}
}
}
Problem is that when I'm passing task which I want to queue (Example 3) each time I pressing button, tasks still are executed at the same time and interrupting each other.
Example 3
[HttpPost(Name = "add-message")]
public async Task<IActionResult> PostMessage([FromBody] MessengerViewModel messengerViewModel)
{
TaskQueue taskQueue = new TaskQueue();
SerialQueue serialQueue = new SerialQueue();
await taskQueue.Enqueue(() => SendMessage(messengerViewModel.PhoneNr, messengerViewModel.MessageBody,
messengerViewModel.ContactId, messengerViewModel.State));
//I'm not running tasks at same time, using one or other at time
await serialQueue.Enqueue(() => SendMessage(messengerViewModel.PhoneNr, messengerViewModel.MessageBody,
messengerViewModel.ContactId, messengerViewModel.State));
return Ok();
}
How could I solve problem and stack task to queue by each click?
Your problem is that you create a new TaskQueueand SerialQueue everytime. Thus each time a user clicks/invokes PostMessage a new queue is created, and the task is the first task in the queue and executed directly.
You should use a static/singleton queue so each click/invoke works on the same queue object.
But that would deliver problems when you scale your webapp across multiple servers. To that end you should use things like (for example) Azure Queue Storage in combination with Azure Functions.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<TaskQueue>();
services.AddSingleton<SerialQueue>();
// the rest
}
SomeController.cs
[HttpPost(Name = "add-message")]
public async Task<IActionResult> PostMessage(
[FromBody] MessengerViewModel messengerViewModel,
[FromServices] TaskQueue taskQueue,
[FromServices] SerialQueue serialQueue)
{
await taskQueue.Enqueue(
() => SendMessage(
messengerViewModel.PhoneNr,
messengerViewModel.MessageBody,
messengerViewModel.ContactId,
messengerViewModel.State));
//I'm not running tasks at same time, using one or other at time
await serialQueue.Enqueue(
() => SendMessage(
messengerViewModel.PhoneNr,
messengerViewModel.MessageBody,
messengerViewModel.ContactId,
messengerViewModel.State));
return Ok();
}

Shared resource and async Web API call

I have a simple Web API method that looks like this:
public async Task<HttpResponseMessage> RunTask(TaskType taskType)
{
var taskId = await TaskManager.CreateTask(taskType);
TaskManager.Run(taskId);
return new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content =
new StringContent($"Task {taskType.GetDescription()} was started.")
};
}
TaskManager.Run is decalared like this:
public async Task Run(int id)
I was expecting it to return "Task was started" message immediately after TaskManager.Run(taskId) But the request continues to run synchronously.
But if to replace the call TaskManager.Run(taskId) with:
Task.Run(() => Thread.Sleep(TimeSpan.FromSeconds(100)));
Then it runs asynchronously.
So I believe this is something to do with the resources shared by TaskManager and main thread. Can a shared resource lock the execution?
I'm using Castle Windsor. One WindsorContainer container is declared in Web API project.
TaskManager utilizes BaseTaskRunner class inside of it. One more WindsorContainer is declared in BaseTaskRunner.
Web API's container uses LifeStyle.PerWebRequest for all components. BaseTaskRunner's container uses LifeStyle.Singleton (not sure if it's correct LifeStyle). Could the call be locked for example by DdContext or other classes declared in both of the containers?
UPD:
I don't want to wait the TaskManager.Run to complete. But what happens is that return statement still waits for the TaskManager.Run to complete (even without await statement on TaskManager.Run).
In other words it does not matter how I call the TaskManager.Run:
TaskManager.Run(taskId);
or
await TaskManager.Run(taskId);
It waits for TaskManager.Run to complete in both cases.
Here is the code of TaskManager:
public class TaskManager : ITaskManager
{
public IRepository<BackgroundTask> TaskRepository { get; set; }
public async Task<int> CreateTask(TaskType type, byte[] data = null, object config = null)
{
var task = new BackgroundTask
{
Type = type,
Status = BackgroundTaskStatus.New,
Config = config?.SerializeToXml(),
Created = DateTime.Now,
Data = data
};
TaskRepository.Add(task);
TaskRepository.SaveChanges();
return task.Id;
}
public async Task Run(int id, bool removeOnComplete = true)
{
var task = TaskRepository.GetById(id);
Run(task, removeOnComplete);
}
public async Task Run(TaskType type, bool removeOnComplete = true)
{
var tasksToRun = TaskRepository.Get(t => t.Type == type);
tasksToRun.ForEachAsync(t => Run(t, removeOnComplete));
}
public async Task Run(BackgroundTask task, bool removeOnComplete = true)
{
switch (task.Type)
{
case TaskType.SpreadsheetImport:
new SpreadsheetImportTaskRunner().Run(task);
break;
}
}
}
And some other classes:
public class SpreadsheetImportTaskRunner : BaseTaskRunner
{
public IForecastSpreadsheetManager SpreadsheetManager { get; set; }
protected override void Execute()
{
SpreadsheetManager.ImportActuals(Task.Data);
}
protected override void Initialize()
{
base.Initialize();
SpreadsheetManager = _container.Resolve<IForecastSpreadsheetManager>();
}
}
BaseTaskRunner:
public class BaseTaskRunner
{
public IRepository<BackgroundTask> TaskRepository { get; set; }
protected IWindsorContainer _container = new WindsorContainer();
protected BackgroundTask Task { get; set; }
public async Task Run(BackgroundTask task)
{
Initialize();
Task = task;
try
{
Execute();
}
catch (Exception ex)
{
SetError(ex.ToString());
}
}
protected virtual void Execute()
{
}
protected virtual void Initialize()
{
_container.Install(new TaskRunnerComponentsInstaller());
TaskRepository = _container.Resolve<IRepository<BackgroundTask>>();
}
}
I still believe this is something to do with the WindsorContainer and common classes which are resolved in several different threads.
The issue is that you're not using await on the Task being returned from the invocation of the TaskManager.Run function. Consider the below:
public async Task<HttpResponseMessage> RunTask(TaskType taskType)
{
var taskId = await TaskManager.CreateTask(taskType);
await TaskManager.Run(taskId);
return new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content =
new StringContent($"Task {taskType.GetDescription()} was started.")
};
}
Now it will work asynchronously as you'd expect. The await sets a continuation marker in the async state-machine, instructing it to return to this portion of the method upon completion of the asynchronous operation defined in the TaskManager.Run.
UPDATE
You are missing lots of await statements, and there are times where you need to not mark methods as async. It appears as though there are some mis-understandings as it pertains to these keywords. Here is what your TaskManager class should look like.
public class TaskManager : ITaskManager
{
public IRepository<BackgroundTask> TaskRepository { get; set; }
public async Task<int> CreateTask(TaskType type,
byte[] data = null,
object config = null)
{
var task = new BackgroundTask
{
Type = type,
Status = BackgroundTaskStatus.New,
Config = config?.SerializeToXml(),
Created = DateTime.Now,
Data = data
};
TaskRepository.Add(task);
TaskRepository.SaveChanges();
return task.Id;
}
public ask Run(int id, bool removeOnComplete = true)
{
var task = TaskRepository.GetById(id);
return Run(task, removeOnComplete);
}
public Task Run(TaskType type, bool removeOnComplete = true)
{
var tasksToRun = TaskRepository.Get(t => t.Type == type);
return tasksToRun.ForEachAsync(t => Run(t, removeOnComplete));
}
public Task Run(BackgroundTask task, bool removeOnComplete = true)
{
switch (task.Type)
{
case TaskType.SpreadsheetImport:
return new SpreadsheetImportTaskRunner().Run(task);
break;
}
}
}
}
Ideally, if the method is marked as a return type of Task and the method doesn't need to unwind any tasks within its execution it can simply return the Task functionality for its implementation. For example, notice how dramatically my TaskManager class differs from yours -- I'm only marking methods as async that need to actually await. These two keywords should be married, if a method uses async there should be an await. But only use await if the method needs to unwind and use the asynchronous operation.

Categories