Introduction
We often come across a scenario where we need to run some function for a long time where this function will react on some trigger and then run some code logic. This is where we need to apply a long running task. This task stays active and responds to a specific trigger. In today’s article we will look at creating such a long running task. We will pass to it the function that needs to run and when the trigger is activated a single run of the function will be executed.
The Long Running Task
In this example, we have created a class as below,
- using System;
- using System.Threading;
- using System.Threading.Tasks;
-
- namespace LongRunningTask
- {
- public class LongTask
- {
- readonly CancellationTokenSource cts = new CancellationTokenSource();
- readonly AutoResetEvent are = new AutoResetEvent(false);
-
- public async Task StartTask(int value, Func<int,Task> act)
- {
- while (true)
- {
- if (cts.IsCancellationRequested) throw new OperationCanceledException();
- await act(value);
- are.WaitOne();
- value++;
- }
- }
-
- public void NextStep()
- {
- are.Set();
- }
-
- public void CancelTask()
- {
- cts.Cancel();
- }
-
- }
- }
In this class, we see that we have a function called Start Task which accepts some initial value that will be used by the executing function. The second parameter is the function itself. This function has an input type of integer (which we are using only as an example here) and the return type of a task. If we will be running a self-isolated task that returns no values, we can simply use an Action delegate as well. We then start an endless loop and do the following steps inside it,
- Add a cancellation check in case we want to cancel the long running task
- Execute the function
- Block further execution using an AutoResetEvent primitive
Now, when we start the task, the passed function will execute once and stop until a signal is received via the “NextStep” function to continue. Once this signal is received, we will call the Set function on the AutoResetEvent primitive and run the function one more time. In this example, we simply increment the input integer value by one. In a real-life scenario, inputs can be updated based on requirements. Also, we have another function called “CancelTask”. When called, we use a CancellationTokenSource to cancel the task.
Example of using the Long Running Task
For demonstration purposes, I have created a Windows Form .NET Core 3.1 application. This application has three labels we will use to start the task, run the next step in the loop and finally cancel the task. For some strange reason, it seems that buttons are missing from the toolbar and hence I decided to use labels. Below is the layout and code for the application,
- using System;
- using System.Diagnostics;
- using System.Threading.Tasks;
- using System.Windows.Forms;
-
- namespace LongRunningTask
- {
- public partial class Form1 : Form
- {
- LongTask lt;
-
- public Form1()
- {
- InitializeComponent();
- }
-
- private void Form1_Load(object sender, EventArgs e)
- {
- SetText(false, false);
- }
-
- private async void StartTask_Click(object sender, EventArgs e)
- {
- SetText(true, true, false);
- try
- {
- lt = new LongTask();
- await Task.Run(() => lt.StartTask(1, LongRunningTask));
- }
- catch
- {
- Debug.WriteLine("An error occured in the application");
- }
- }
-
- private void StopTask_Click(object sender, EventArgs e)
- {
- lt.CancelTask();
- NextTask_Click(this,null);
- SetText(false, false);
- }
-
-
- private void NextTask_Click(object sender, EventArgs e)
- {
- lt.NextStep();
- }
-
- private void SetText(bool nextRun, bool stopTask, bool startTask = true)
- {
- NextRun.Enabled = nextRun;
- StopTask.Enabled = stopTask;
- StartTask.Enabled = startTask;
- }
-
- private async Task LongRunningTask(int value)
- {
- Debug.WriteLine($"Task executed = {value}");
- await Task.Delay(1000);
- }
-
- }
- }
As you can see we have a function called “LongRunningTask” which simply writes the value of an integer to the output window. We pass this function to the StartTask function of the LongTask class. This function is executed once and then it waits for the user to either click the label “Run next Loop Item” or the “Stop Task” label. If the “Run next Loop Item” label is clicked, the function is executed one more time and waits again. If the “Stop Task” label is clicked, the running loop is cancelled.
We can see this in action as below,
Summary
In this article, we looked at how to create a generic long running task class to which we can pass a function which executes on a trigger, which is also run by the client. I have used a simple Windows form example as the client in this article. However, this generic LongTask class can be used by other clients like other classes, services etc. This can be used to create a sort of service which is processing a list. We will call the function via the Long running task class and wait until it is completed. When the function is completed and the first item is processed, we can make a call back to the client class, which will then trigger the function again and this time the second item is executed. This model runs one function at one time. If we wanted to increase it to multiple tasks at the same time, we can use the Task Parallel Library and “Wait All” function for that.