【问题标题】:Wrapper method for Task.Factory.StartNew to execute custom methods with different parameters and return valuesTask.Factory.StartNew 的包装方法,用于执行具有不同参数和返回值的自定义方法
【发布时间】:2014-12-10 17:43:59
【问题描述】:

我正在使用 .NET 4.0C# 中开发一个 MDI 应用程序。我创建了一个包装类TaskManager 来管理一些方法在不同任务上的执行。

所以我可以打电话:

_taskManager.StartNewTask(MethodName);

我需要在单独的Task 上执行每一种紧张的工作方法。

我创建了这个包装类以避免对Task.Factory.StartNew() 的调用分散在代码周围,因此保持干净,并且能够以某种方式跟踪线程。

我的问题是,现在我正在尝试取消Task,例如如果用户按ESC 键Task 会中止。为此,我需要使用CancellationToken 并将其作为参数添加到我的所有方法中。然后必须在每个方法体中检查它。例如:

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();
    }
}

现在,我的TaskManager.StartNewTask() 逻辑是这样的:

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;
}

我想要什么:

  1. 我需要更改TaskManager.StartNewTask()的逻辑才能将CancellationToken传递给方法但我不知道该怎么做...
  2. 我还想知道是否有可能创建一个更更通用 TaskManager.StartNewTask() 的方法,该方法可以使用任意数量的输入参数来执行任何类型的方法> 以及任何类型的返回值

我需要这样的东西:

// 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;
}

更新 1(针对问题 n.2) (更改了自定义方法)

如果我必须执行类似的方法

int CustomMethod (int a, int b, CancellationToken ct)

在使用我的TaskManager.StartNewTask() 方法的新Task 中,我应该如何更改StartNewTask() 以及我应该如何拨打电话?

类似

int result = taskManager.StartNewTask(CustomMethod(<input parameters here>));

代码可能是这样的

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;
    }
}

更新 2

在为我的问题 2 尝试了 @Sriram Sakthivel 建议后,我现在有了以下代码:

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;
    }
}

它让我回到 2... 但是 a = 1 和 b = 3... 所以总和应该是 4!

这可能与TaskManager.StartNewTask() 的返回类型有关...但是我应该如何管理我在新任务中执行的方法的返回值?怎么了?

【问题讨论】:

  • 看看你从StartNewTask 方法返回了什么?您返回 TaskId 而不是计算结果。要启用返回值,您需要 Func&lt;CancellationToken, int&gt; 而不是 Action&lt;CancellationToken&gt;
  • 是的,没错。但是现在,我该如何管理 Task Id 的返回以及我正在运行的方法的返回值?
  • 创建一个新类来存储两者并返回其实例,或使用输出参数。我建议第一个选项。
  • 阅读 (here) 我找到了一种通过使用 Task&lt;type&gt; (Task&lt;int&gt; 等) 从任务内部运行的方法获取结果的方法。所以电话应该是:Task&lt;int&gt; myTask = Task&lt;int&gt;.Factory.StartNew(method, ct)。现在,如果我想让我的TaskManager.StartNewTask() 更通用并且能够接受任何类型的方法作为参数并返回该方法返回的任何类型的值,我应该如何继续?类似于: public Task StartNewTask(Func method)...

标签: c# multithreading task-parallel-library task cancellation-token


【解决方案1】:

我猜你只需要Action&lt;CancellationToken&gt;,除非我误解了。

public int StartNewTask(Action<CancellationToken> method)
{
    try
    {
        CancellationToken ct = _cts.Token;
        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;
}

对于您的问题 2:您可以使用闭包来访问创建匿名方法或 lambda 的方法的周围环境。否则,您打算如何传递 n 个参数?你从哪里得到它们?

您可以使用闭包来解决此问题。 Jon 解释闭包here

int result = _taskManager.StartNewTask((ct)=> CustomMethod(paramA, paramB,ct));

【讨论】:

  • 问题 1:是的,它有效!问题 2:您能否更明确地说明“使用 clousures 访问方法的周围环境”的含义?您可以在答案中添加代码示例吗?假设我想用我的TaskManager.StartNewTask() 方法执行一个int CustomMethod (string a, double b, CancellationToken ct) 方法。我应该如何更改我的StartNewTask() 以及如何拨打电话int return = taskManager.StartNewTask(CustomMethod(&lt;input parameters here&gt;));
  • @RainbowCoder 我会更新我的帖子,但是你打算如何传递参数,我不知道。您将从哪里获得这些任意参数?
  • 我已经更新了我的答案,看看是否有帮助。虽然没有测试。
  • 它可以工作,但我有一些奇怪的行为......我会更新这个问题的代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-27
  • 1970-01-01
相关资源
最近更新 更多