I'm developing an MDI application in C# with .NET 4.0. I created a wrapper class TaskManager
to manage the execution of some methods on separate Tasks.
So I can call:
_taskManager.StartNewTask(MethodName);
with every intense work method that I need to execute on a separeted Task
.
I created this wrapper class to avoid calls to Task.Factory.StartNew()
scattered all around the code and so keep it clean and also to be able to somehow keep track of threading.
My problem is that now I'm trying to implement the cancellation of Task
, such as if the user press ESC key the Task
aborts. To do this I need to use the CancellationToken
and add it as a parameter to all of my methods. It then has to be checked within each method body. For example:
private void MethodName(CancellationToken ct)
{
// Verify cancellation request
if (ct.IsCancellationRequested)
{
// Log the cancellation request "The task was cancelled before it got started"
ct.ThrowIfCancellationRequested();
}
// Do the heavy work here
// ...
// At some critic point check again the cancellation request
if (ct.IsCancellationRequested)
{
// Log the cancellation request "The task was cancelled while still running"
ct.ThrowIfCancellationRequested();
}
}
Now, my TaskManager.StartNewTask()
logic is this:
public int StartNewTask(Action method)
{
try
{
CancellationToken ct = _cts.Token;
Task task = Task.Factory.StartNew(method, ct);
_tasksCount++;
_tasksList.Add(task.Id, task);
return task.Id;
}
catch (Exception ex)
{
_logger.Error("Cannot execute task.", ex);
}
return -1;
}
What I'd want:
- I need to change the logic of
TaskManager.StartNewTask()
to be able to pass the CancellationToken to the method but I don't know how to do it... - I'd also like to know if it is possible to create an even more generic
TaskManager.StartNewTask()
method that can execute any kind of method with any number of input parameters and also any kind of return value.
I need something like this:
// I don't know ho to change the method signature to accept
// methods with parameters as parameter...
public int StartNewTask(Action method)
{
try
{
CancellationToken ct = _cts.Token;
// Here I need to pass the CancellationToken back to the method
// I know that this can't be the way...
Task task = Task.Factory.StartNew(method(ct), ct);
_tasksCount++;
_tasksList.Add(task.Id, task);
return task.Id;
}
catch (Exception ex)
{
_logger.Error("Cannot execute task.", ex);
}
return -1;
}
UPDATE 1 (for Question n. 2) (changed the CustomMethod)
If I have to execute a method like
int CustomMethod (int a, int b, CancellationToken ct)
within a new Task
using my TaskManager.StartNewTask()
method, how should I change StartNewTask()
and how should I make the call?
Something like
int result = taskManager.StartNewTask(CustomMethod(<input parameters here>));
The code could be something like this
public partial class MyForm : Form
{
private readonly TaskManager _taskManager;
public MyForm()
{
InitializeComponent();
_taskManager = TaskManager.GetInstance();
}
private void btnOK_Click(object sender, EventArgs e)
{
// This is the call to StartNewTask()
// where now I need to set the parameters for CustomMethod()
// Input parameters could be class variables or a custom object
// with specific Properties such as:
// MyObject.MyString, MyObject.MyDouble
int result = _taskManager.StartNewTask(CustomMethod<input parameters here>);
// Do something with my result...
MessageBox.Show("Result: " + result, "Operation", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private int CustomMethod(int a, int b, CancellationToken ct)
{
if (ct.IsCancellationRequested)
{
ct.ThrowIfCancellationRequested();
}
int result = -1;
// Do some heavy work with int 'a', int 'b' and produce result...
// Meanwhile check again for cancel request
return a + b;
}
}
UPDATE 2
After trying @Sriram Sakthivel suggestion for my question 2, I've now this code:
private void btnOK_Click(object sender, EventArgs e)
{
// a=1 and b=3
int result = _taskManager.StartNewTask((ct) => CustomMethod(1, 3, ct));
// On the MessageBox I get 2...
MessageBox.Show("Result: " + result, "Operation", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private int CustomMethod(int a, int b, CancellationToken ct)
{
if (ct.IsCancellationRequested)
{
ct.ThrowIfCancellationRequested();
}
// a=1 and b=3, so the sum must me 4...
return a + b;
}
public class TaskManager
{
private static readonly TaskManager Instance = new TaskManager();
private readonly Dictionary<int, Task> _tasksList;
private static int _tasksCount;
private static CancellationTokenSource _cts;
public int StartNewTask(Action<CancellationToken> method)
{
try
{
CancellationToken ct = _cts.Token;
Task task = Task.Factory.StartNew(() => method, ct);
_tasksCount++;
_tasksList.Add(task.Id, task);
return task.Id;
}
catch (Exception ex)
{
_logger.Error("Cannot execute the task.", ex);
}
return -1;
}
}
It gets me back 2... but a = 1 and b = 3... so the sum should be 4!
It probably has to do with the return type of TaskManager.StartNewTask()
... But how should I manage the return values of the methods that I execute in new Tasks? What's wrong?
I guess you simply need
Action<CancellationToken>
unless I misunderstood.For your question 2: You can use closures to access the surrounding of the method where the anonymous method or lambda is created. Otherwise, how do you plan to pass the n number of arguments? where do you get them from?
You can use closures to workaround this. Jon explains closures here