让 Task 使用已知的特定 return 类型调用任意函数的正确方法是什么?

What's the proper way to have a Task that calls an arbitrary function with a known, specific return type?

我有一个计算成本很高的值,可以提前询问 - 类似于延迟启动的值,其初始化实际上是在定义时完成的,但在不同的线程中。我的直接想法是使用 parallelism.-Task 似乎是专门为这个确切的用例而构建的。所以,让我们把它放在 class:

class Foo
{
    import std.parallelism : Task,task;
    static int calculate(int a, int b)
    {
        return a+b;
    }
    private Task!(calculate,int,int)* ourTask;
    private int _val;
    int val()
    {
        return ourTask.workForce();
    }
    this(int a, int b)
    {
        ourTask = task!calculate(a,b);
    }
}

这似乎一切都很好......除非我希望任务基于非静态方法,在这种情况下我想使任务成为委托,在这种情况下我开始不得不做像这样的东西:

private typeof(task(&classFunc)) working;

then,事实证明,typeof(task(&classFunc)),当它在函数体外部被请求时,实际上是Task!(run,ReturnType!classFunc function(Parameters!classFunc))*,你可能注意不是那个的运行时函数调用实际return编辑的类型。那将是 Task!(run,ReturnType!classFunc delegate(Parameters!classFunc))*,这要求我在实际调用 task(&classFunc) 时转换为 typeof(working)。这都是极度骇人听闻的感觉。

这是我对通用模板解决方案的尝试:

/** 
    Provides a transparent wrapper that allows for lazy
    setting of variables. When lazySet!!func(args) is called
    on the value, the function will be called in a new thread;
    as soon as the value's access is attempted, it'll return the
    result of the task, blocking if it's not done calculating.

    Accessing the value is as simple as using it like the
    type it's templated for--see the unit test.
*/
shared struct LazySet(T)
{

    /// You can set the value directly, as normal--this throws away the current task.
    void opAssign(T n)
    {
        import core.atomic : atomicStore;
        working = false;
        atomicStore(_val,n);
    }

    import std.traits : ReturnType;
/** 
    Called the same way as std.parallelism.task;
    after this is called, the next attempt to access
    the value will result in the value being set from
    the result of the given function before it's returned.
    If the task isn't done, it'll wait on the task to be done
    once accessed, using workForce.
*/
    void lazySet(alias func,Args...)(Args args)
        if(is(ReturnType!func == T))
    {
        import std.parallelism : task,taskPool;
        auto t = task!func(args);
        taskPool.put(t);
        curTask = (() => t.workForce);
        working = true;
    }
    /// ditto
    void lazySet(F,Args...)(F fpOrDelegate, ref Args args)
        if(is(ReturnType!F == T))
    {
        import std.parallelism : task,taskPool;
        auto t = task(fpOrDelegate,args);
        taskPool.put(t);
        curTask = (() => t.workForce);
        working = true;
    }

    private:
        T _val;
        T delegate() curTask;
        bool working = false;

        T val()
        {
            import core.atomic : atomicStore,atomicLoad;
            if(working)
            {
                atomicStore(_val,curTask());
                working = false;
            }
            return atomicLoad(_val);
        }
    // alias this is inherently public
    alias val this;
}

这让我可以使用 returns T 的任何函数、函数指针或委托来调用 lazySet,然后它会并行计算值并 return 它完全计算得出,下次有任何东西试图访问底层值时,完全按照我的意愿进行。我写的单元测试来描述它的功能通过等,它完美地工作。

但是有一件事困扰着我:

        curTask = (() => t.workForce);

通过现场创建一个 lambda 来移动 Task 碰巧 在其上下文中包含 Task 看起来仍然像我正在尝试在语言上“拉一个过来”,即使它比之前的所有演员都少“hackish-feeling”。

我是否遗漏了一些明显的语言功能,这些功能可以让我更“优雅地”执行此操作?

采用 alias 函数参数的模板(例如 Task 系列)对其实际类型很挑剔,因为它们可以接收任何类型的函数作为参数(包括自行推断的就地委托).由于被调用的实际函数是类型本身的一部分,您必须将其传递给自定义结构才能直接保存任务。

至于您的解决方案的合法性,存储 lambda 以便稍后与复杂(或“隐藏”)类型交互没有任何问题。

另一种方法是直接存储指向 &t.workForce 的指针。

此外,在您的 T val() 中,两个线程可以同时进入 if(working),但我想由于原子存储,它不会真正破坏任何东西 - 无论如何,可以修复通过 core.atomic.cas.