I have an application which currently runs Tasks on a time interval, however I would like more control over that, to be able to stop a running task and restart it by clicking a UI.
There are 6 tasks at the moment, but I would want to keep things generic, to be able to easily more when required. I was hoping to be able to create a wrapper to control them, which I can pass a method into as a parameter.
As such I created an object, which I create as many of as there are tasks, I can get status updates from it as well as manage it
I want to:
- Start a method/Task
- Stop a method/Task
- Restart a method/Task
- Get feedback from it's log/updates/progress/errors that I record to updates List
Is this a good way to do this, is there a better way to achieve what I'm after?
public class ManagedTask
{
public ManagedTask()
{
CreateNewToken();
}
public int Id { get; set; }
public string DescriptiveName { get; set; }
public Action<CancellationToken> TheVoidToRun { private get; set; }
private CancellationTokenSource CTokenSource { get; set; }
private CancellationToken CToken { get; set; }
private Task TheRunningThing { get; set; }
public void StartIt()
{
if (TheRunningThing == null || TheTaskStatus() == TaskStatus.Canceled || TheTaskStatus() == TaskStatus.RanToCompletion)
{
CreateNewToken();
}
// Start up the Task
AddUpdate($"Starting Task at {DateTime.Now}");
TheRunningThing = Task.Run(() => TheVoidToRun?.Invoke(CToken), CToken);
AddUpdate($"Started Task at {DateTime.Now}");
}
public void EndIt()
{
AddUpdate($"Cancelling Task at {DateTime.Now}");
CTokenSource.Cancel();
// Do - If in progress try to stop (Cancellation Token)
// Do - Stop future repeats
}
private void CreateNewToken()
{
CTokenSource = new CancellationTokenSource();
CTokenSource.Token.ThrowIfCancellationRequested();
CToken = CTokenSource.Token;
}
public TaskStatus TheTaskStatus() => TheRunningThing.Status;
internal List<string> Updates { get; set; }
private void AddUpdate(string updates)
{
// Do stuff
}
}
So I have various methods which I'd like to pass into this such like:
public class AvailableTasks
{
public async void DoStuffThatIsCancelable(CancellationToken token)
{
DoTheLongStuffOnRepeat(token);
}
public async void DoAnotherThingThatIsCancelable(CancellationToken token)
{
DoTheLongStuffOnRepeat(token);
}
private async void DoTheLongStuffOnRepeat(CancellationToken token)
{
// Do stuff
for (int i = 0; i < 20; i++)
{
while (!token.IsCancellationRequested)
{
try
{
await Task.Delay(500, token);
}
catch (TaskCanceledException ex)
{
Console.WriteLine("Task was cancelled");
continue;
}
Console.WriteLine($"Task Loop at {(i + 1) * 500}");
}
}
}
}
Here is how I was thinking of calling it.
private static readonly List<ManagedTask> _managedTasks = new List<ManagedTask>();
public static void SetupManagedTasks()
{
var at = new AvailableTasks();
var mt1 = new ManagedTask
{
Id = 1,
DescriptiveName = "The cancelable task",
TheVoidToRun = at.DoStuffThatIsCancelable,
};
_managedTasks.Add(mt1);
var mt2 = new ManagedTask
{
Id = 2,
DescriptiveName = "Another cancelable task",
TheVoidToRun = at.DoAnotherThingThatIsCancelable,
};
_managedTasks.Add(mt2);
mt1.StartIt();
mt2.StartIt();
Console.WriteLine($"{mt1.DescriptiveName} status: {mt1.TheTaskStatus()}");
Console.WriteLine($"{mt2.DescriptiveName} status: {mt2.TheTaskStatus()}");
}
public static void CancelTask(int id)
{
var mt = _managedTasks.FirstOrDefault(t => t.Id == id);
if (mt != null)
{
mt.EndIt();
Console.WriteLine($"{mt.DescriptiveName} status: {mt.TheTaskStatus()}");
}
}
public static void GetTaskStatus(int id)
{
var mt = _managedTasks.FirstOrDefault(t => t.Id == id);
if (mt != null)
{
Console.WriteLine($"{mt.DescriptiveName} status: {mt.TheTaskStatus()}");
}
}
However even with all the above, I suffer from the Status only ever showing RanToCompletion.
How can I structure the above to achieve what I want?
Thanks,
David
I suffer from the Status only ever showing RanToCompletion.
This is because your methods are using async void. They should be async Task. As I describe in my async best practices article, you should avoid async void.
Other notes...
Start a method/Task
Restart a method/Task
You can start (or restart) a task on the thread pool by using Task.Run. However, if you have naturally asynchronous tasks, then you can represent them as Func<Task> and just invoke the Func<Task> to start them.
Stop a method/Task
The only appropriate way to do this is with a CancellationToken, which it looks like you're using correctly.
Get feedback from it's log/updates/progress/errors that I record to updates List
I recommend using IProgress<T> for any kind of progress updates.
Related
Why does the Status of my Task return "WaitingForActivasion" instead of "Running" ?
If I remove Task.Run I get stuck in the while loop, so I assume its not running asynchronous.
public class StateManagerTest
{
[Fact]
public void Start_TaskStatus()
{
StateManager manager = new StateManager();
manager.Start();
Assert.True(manager.Status == System.Threading.Tasks.TaskStatus.Running.ToString());
}
}
public class StateManager
{
private CancellationTokenSource cts = new();
private Task updateTask;
public HashSet<StateItem> StateItems { get; private set; }
public Provider Provider { get; private set; }
public List<OutputService> OutputServices { get; private set; }
public string Status
{
get => updateTask.Status.ToString();
}
public StateManager()
{
StateItems = new();
OutputServices = new();
Provider = new();
}
public void Stop()
{
cts.Cancel();
}
public void Start()
{
updateTask = Task.Run(() => Update(cts.Token))
.ContinueWith(t => Debug.WriteLine(t.Exception.Message), TaskContinuationOptions.OnlyOnFaulted);
}
private async Task Update(CancellationToken token)
{
while (true)
{
// get changes from outputs
Dictionary<StateItem, object> changes = new Dictionary<StateItem, object>();
foreach (var service in OutputServices)
{
var outputChanges = await service.GetChanges();
foreach (var change in outputChanges)
changes.TryAdd(change.Key, change.Value);
}
// write changes to provider source
await Provider.PushChanges(changes);
// update state
await Provider.UpdateStateItems();
// update all services
foreach (var service in OutputServices)
await service.UpdateSource();
if (token.IsCancellationRequested)
return;
}
}
}
As others have noted, WaitingForActivation is the correct state for a Promise Task that is not yet completed. In general, I recommend not using Task.Status or ContinueWith; they are relics from a time before async/await existed.
How to get status of long running task
I believe you would want progress reporting, which is done yourself. The T in IProgress<T> can be a string if you want a simple text update, or a double if you want a percentage update, or a custom struct if you want a more complex update.
I am not sure if I am missing something here but more for loop seems to be executing synchronously even though I await all tasks out side of it.
Here is my code below:
static void Main(string[] args) {
var t = Start();
}
public static async Task < List < Task < TaskInfo >>> Start() {
var listOfTasks = new List < Task < TaskInfo >> ();
for (var i = 0; i <= 100; i++) {
var process = new Processor();
listOfTasks.Add(process.Process(i));
}
await Task.WhenAll(listOfTasks);
return listOfTasks;
}
I pass in the taskId to log out just to see the order the tasks execute.
Am I missing something really obvious here?
EDIT:
Changed code to this based on the answers and comments below and it still appears synchronously:
public class StartWork
{
public int TaskId { get; set; }
public Processor Processor { get;}
public StartWork()
{
Processor = new Processor();
}
}
static void Main(string[] args)
{
var t = Start();
}
public static async Task<TaskInfo[]> Start()
{
var tasks = new List<StartWork>();
for (int i = 1; i < 100; i++)
{
var work = new StartWork
{
TaskId = i
};
tasks.Add(work);
}
return await Task.WhenAll(tasks.Select(i => i.Processor.Process(i.TaskId)));
}
The function I am calling in processor class:
public Task<TaskInfo> Process(int taskId)
{
try
{
taskId = taskId + 1;
stopwatch.Start();
using (var bus = RabbitHutch.CreateBus(xxDev))
{
#event = new AutoResetEvent(false);
var replyTo = Guid.NewGuid().ToString();
var messageQueue = bus.Advanced.QueueDeclare(replyTo, autoDelete: true);
bus.Advanced.Consume(messageQueue, (payload, properties, info) =>
{
ReceivePdf(payload, properties, info);
return Task.FromResult(0);
});
taskInfo.InputFile = inputFile;
var html = File.ReadAllText(inputFile);
taskInfo.Html = html;
var message = PrepareMessage(new RenderRequest()
{
Html = Encoding.UTF8.GetBytes(html),
Options = new RenderRequestOptions()
{
PageSize = "A4",
ImageQuality = 70,
PageLoadRetryAttempts = 3
}
});
var correlation = Guid.NewGuid().ToString();
Console.WriteLine($"CorrelationId: {correlation}, TaskId {taskId}");
var props = new MessageProperties
{
CorrelationId = correlation,
ReplyTo = replyTo,
Expiration = "6000"
};
Publish(bus, props, message);
taskInfo.CorrelationId = Guid.Parse(correlation);
#event.WaitOne();
stopwatch.Stop();
taskInfo.TimeTaken = stopwatch.Elapsed;
return Task.FromResult(taskInfo);
}
}
catch (Exception e)
{
taskInfo.OutputFile = Empty;
return Task.FromResult(taskInfo);
}
}
void ReceivePdf(byte[] payload, MessageProperties properties, MessageReceivedInfo info)
{
var file = Format(outputFile, properties.CorrelationId);
taskInfo.OutputFile = file;
Console.WriteLine("Output written to " + file);
File.WriteAllBytes(file, payload);
var remaining = Interlocked.Decrement(ref outstandingRequests);
if (remaining == 0)
{
#event.Set();
}
}
This is a synchronous task
listOfTasks.Add(process.Process(i));
You are just adding items to the list.
This is also a synchronous task
process.Process(i);
The task that the function above returns is asynchronous and it will execute asynchronously in the whenAll call of your code.
Bear in mind that when all will wait for all tasks to run and if the task is trivial, since they start one after the other, will most times run sequentially by chance.
You will see some difference if the task code executed differentiated in execution time based on input.
First async doesn't mean multithread, async is used to run background task without blocking UI or to run I/O operations without bloking main thread.
Usually the operating system handles async with multithreading, but there is no guarantee.
If you want be sure to start multiple threads use Thread.Start.
Anyway in your code you force your code to run synchronously, because you call the async method start in the Main method without await.
You need to change the code to:
static async void Main(string[] args)
{
var t = await Start();
}
or without waiting to (but the program risk to terminate before the task complete):
static void Main(string[] args)
{
Task.Run(async () => {
var t = await Start();
});
}
I try to wait for the class to be finished with instantiate.
My architecture is the following. Cook is inheriade from CookChief.
And if I instantiate cook, CookChief is creating himself, but CookChief is calling 1 other class named Cookhelper the cookhelper is waiting for a input and for this input method i want to wait in Cook.
The thing is iam creating this in MVVM Galasoft and my entry point is the CookViewmodel, with a relaycommand.
In the code below you can see my architecture. To say it short I want to wait until this bool processed = await Task.Run(() => ValidateForDeviceId()); is finished.
My first step was to outsource the constructer of each class. And create a init method.
This is my code:
public CookViewModel()
{
startCookButtonCommand = new RelayCommand(Cook);
}
private async Task Cook()
{
cook.Init();
}
public class Cook : CookChief
{
public Cook()
{
}
public async Task Init()
{
await this.CookChiefInit();
//here I want to wait until CookChiefInit is finished
Cooking();
}
public void Cooking()
{
MessageBox.Show("Input received");
}
}
Now the Cookchief:
public Cookchief()
{
}
protected async Task CookchiefInit()
{
this.Cookhelper = new Cookhelper();
Cookhelper.CookHelperInit();
}
And in the CookHelper we do this:
public CookHelper()
{
}
public void CookHelperInit()
{
this.driverWindow = new DriverWindow();
startProc();
}
private async void startProc()
{
ShowOrCloseDriverWindow(true);
//this is the task what we wait for before we can repeat
bool processed = await Task.Run(() => ValidateForDeviceId());
if(processed)
{
ShowOrCloseDriverWindow(false);
}
else
{
MessageBox.Show("DriverError");
}
}
private bool ValidateForDeviceId()
{
for (; ; )
{
this.deviceId = Input.deviceId;
if (deviceId > 0)
{
break;
}
}
return true;
}
Per the discussion in the comments, the problem here was that the initialization routine mixed synchronous and asynchronous methods and calls. Additionally, some async methods were called without the await keyword. The solution was to make all calls asynchronous and await them.
cook.Init() needs an await:
private async Task Cook()
{
await cook.Init();
}
In CookchiefInit(), the CookHelperInit() call needs to be awaited:
protected async Task CookchiefInit()
{
this.Cookhelper = new Cookhelper();
Cookhelper.CookHelperInit();
}
In order to await CookHelperInit(), it needs to be made asynchronous. The startProc() call is to an async method, so it must also be awaited:
public async Task CookHelperInit()
{
this.driverWindow = new DriverWindow();
await startProc();
}
I am experiencing some weird behaviour with a windows service application I am working on. This is my 1st dip into Tasks so I am on a steep learning curve and in need of some assistance as I know my issue is probably down to something I have misunderstood.
I have the following setup:
public partial class MyService
{
protected override void OnStart(string[] args)
{
MasterTokenSource = new CancellationTokenSource();
MasterCancellationToken = MasterTokenSource.Token;
//Begin tasks.
StartAllTasks();
//This is the thread that is going to listen for updates in the database.
Task MasterService = Task.Factory.StartNew(() =>
{
while (!MasterCancellationToken.IsCancellationRequested)
{
//Sleep for the amount of time as determined in the DB
Thread.Sleep(ServiceInstance.PollInterval * 1000);
Console.WriteLine("Polled for changes");
//Check service modules for changes as per DB config
UpdateServiceModulePropertiesAndRunningTasks();
//MasterTokenSource.Cancel();
}
MasterCancellationToken.ThrowIfCancellationRequested();
}, MasterCancellationToken);
}
private void StartAllTasks()
{
//Index pages task
ServiceModule PageIndexersm = ServiceInstance.GetServiceModule("PageIndexer");
PageIndexer.StartNewInstance(PageIndexersm, ConfigInstance, MasterTokenSource);
//There are other calls to other methods to do different things here but they all follow the same logic
}
private void UpdateServiceModulePropertiesAndRunningTasks()
{
//Get a fresh copy of the service instance, and compare to current values
ServiceInstance compareServiceInstance = new ServiceInstance(ConfigInstance.OneConnectionString, ConfigInstance.TwoConnectionString, ConfigInstance.ServiceName);
foreach (ServiceModule NewServiceModuleItem in compareServiceInstance.AllServiceModules)
{
ServiceModule CurrentServiceModuleInstance = ServiceInstance.GetServiceModule(NewServiceModuleItem.ModuleName);
if (!NewServiceModuleItem.Equals(CurrentServiceModuleInstance))
{
//Trigger changed event and pass new instance
CurrentServiceModuleInstance.On_SomethingChanged(NewServiceModuleItem, MasterTokenSource);
}
}
}
}
public class PageIndexer
{
public ServiceConfig ServiceConfig { get; set; }
public ServiceModule ServiceModuleInstance { get; set; }
public Guid InstanceGUID { get; set; }
public CancellationTokenSource TokenSource { get; set; }
public CancellationToken Token { get; set; }
public PageIndexer(ServiceModule PageIndexerServiceModule, ServiceConfig _ServiceConfig)
{
ServiceModuleInstance = PageIndexerServiceModule;
ServiceModuleInstance.SomethingChanged += ServiceModuleInstance_SomethingChanged;
ServiceConfig = _ServiceConfig;
InstanceGUID = Guid.NewGuid();
}
//This is the method called within the PageIndexer instance
private void ServiceModuleInstance_SomethingChanged(ServiceModule sm, CancellationTokenSource MasterCancelToken)
{
Console.WriteLine(InstanceGUID + ": Something changed");
TokenSource.Cancel();
//Start new indexer instance
PageIndexer.StartNewInstance(sm, ServiceConfig, MasterCancelToken);
}
public void RunTask()
{
Console.WriteLine("Starting Page Indexing");
Task.Factory.StartNew(() =>
{
while (true)
{
if (TokenSource.Token.IsCancellationRequested)
{
Console.WriteLine(InstanceGUID + ": Page index CANCEL requested: " + TokenSource.IsCancellationRequested);
TokenSource.Token.ThrowIfCancellationRequested();
}
if (ServiceModuleInstance.ShouldTaskBeRun())
{
Console.WriteLine(InstanceGUID + ": RUNNING full index, Cancellation requested: " + TokenSource.IsCancellationRequested);
RunFullIndex();
}
else
{
Console.WriteLine(InstanceGUID + ": SLEEPING, module off, Cancellation requested: " + TokenSource.IsCancellationRequested);
//If the task should not be run then sleep for a bit to save resources
Thread.Sleep(5000);
}
}
}, TokenSource.Token);
}
public static void StartNewInstance(ServiceModule serviceModule, ServiceConfig eServiceConfig, CancellationTokenSource MasterCancellationToken)
{
PageIndexer pageIndexerInstance = new PageIndexer(serviceModule, eServiceConfig);
CancellationTokenSource NewInstanceCancellationTokenSource = new CancellationTokenSource();
NewInstanceCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(MasterCancellationToken.Token);
pageIndexerInstance.TokenSource = NewInstanceCancellationTokenSource;
pageIndexerInstance.Token = pageIndexerInstance.TokenSource.Token;
pageIndexerInstance.RunTask();
}
}
What I am seeing is that the cancel and start are working fine for me for the 1st change detected but subsequent cancels issued after other changes are not working. I can see the call to the event method happening, however, it appears to be calling on the original instance of the page indexer.
I am sure I have just got to a point where I have been going around so long I have made a complete mess, but I would be grateful for any guidance anyone can offer to get me back on the right track
Thank you in advance.
Regards
A CancellationTokenSource and CancellationToken can only be signaled once. They become cancelled forever. If you want multiple cancellation signals for multiple threads/tasks then you need one token for each such operation.
Often, it is a good pattern to group them in a class:
class MyOperation {
Task task; //use this for waiting
CancellationTokenSource cts; //use this for cancelling
}
That way there automatically is a 1:1 association of task and token. You are able to cancel a specific task this way.
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.