使用预定义对象减少 Task.Run/Factory.StartNew 中的闭包开销

问题描述 投票:0回答:2

这纯粹是为了实验目的和/或学习练习。本质上,我想看看是否可以通过创建一个只初始化一次的类来减少使用

Task.Run(()=>Func<>())
时创建的闭包的占用空间。第一,目标是避免每次运行时创建一个“新”实例,这可能比我想象的闭包本身效率低(但这只是猜测,我知道)。因此,创建一个基本类来执行此操作相当简单,因为您可以在堆栈上找到相关示例。

但是,我遇到的问题是,在我看来,如果我想使用另一个类中的成员和函数,则必须封装它们,或者将它们注入到我们要使用的类中

Run
继续,虽然它可能比原始类本身的数据少,但它可能不会有太大的改进。

所以说,我有一些类似的东西:

internal async Task<PathObject> PopulatePathObjectAsync(Vector3Int origin, Vector3Int destination, PathObject path)
{
    return await Task.Factory.StartNew(() => PopulatePathObject(origin, destination, path));
}

/// Not sure if we want to make this a task or not because we may just parallelize and await the outer task.
/// We'll have to decide when we get down to finalization of the architecture and how it's used.
internal PathObject PopulatePathObject(Vector3Int origin, Vector3Int destination, PathObject path)
{
    Debug.Log($"Pathfinding Search On Thread: ({System.Threading.Thread.CurrentThread.ManagedThreadId})");

    if (!TryVerifyPath(origin, destination, ref path, out PathingNode currentNode))
        return path;

    var openNodes = m_OpenNodeHeap;

    m_ClosedNodes.Clear();

    openNodes.ClearAndReset();
    openNodes.AddNode(currentNode);

    for (int i = CollectionBufferSize; openNodes.Count > 0 && i >= 0; i--)
    {
        currentNode = ProcessNextOpenNode(openNodes);

        if (NodePositionMatchesVector(currentNode, destination))
        {
            return path.PopulatePathBufferFromOriginToDestination(currentNode, origin, PathState.CompletePath);
        }

        ProcessNeighboringNodes(currentNode, destination);
    }

    return path.PopulatePathBufferFromOriginToDestination(currentNode, origin, PathState.IncompletePath);
}

为了放弃 lambda、闭包和委托的创建(或者可能强制转换?),我需要一个实际上完整封装该

PopulatePathObject
函数的类,或者通过逐字复制必要的成员,或者将它们作为参数传递。这一切似乎都可能带来任何好处。那么有什么办法我可以拥有类似的东西..

private class PopulatePathObjectTask
{
    private readonly Vector2Int m_Origin;
    private readonly Vector3Int m_Destination;
    private readonly PathObject m_Path;

    public PopulatePathObjectTask(Vector2Int origin, Vector3Int destination, PathObject path)
    {
        m_Origin = origin;
        m_Destination = destination;
        m_Path = path;
    }

    public PathObject PopulatePathObject(Vector3Int origin, Vector3Int destination, PathObject path)
    {
        ///Obviously here, without access to the actual AStar class responsible for the search,
        ///I don't have access to the functions or the class members such as the heap or the hashset
        ///that represents the closed nodes as well as the calculated buffer size based on the space-state
        ///dimensions. With that, I'd just be recreating the class and not avoiding much, if any,
        ///of the overhead created by the closure capturing the class in the first place.
    }
}

我可以用来访问已经存在的功能吗?我一直在考虑创建静态成员并对开放/封闭节点集合使用依赖注入的想法,但我认为,或者更确切地说希望,有人可能对此有更多的了解,除了它毫无意义,甚至可能的开销减少或性能提升将非常小,以至于毫无意义。假设你可能是对的,但我这样做是为了练习,我希望能够实际测量差异。我可能甚至不会使用它,甚至可能会放弃 AStar 而选择 JPS,但在继续之前我想知道。我不完全确定,但似乎闭包必须及时捕获整个 AStar 对象,人们希望通过引用来实现这一点。

c# closures task-parallel-library
2个回答
1
投票
您可以创建一个对象并将该对象的方法传递给 Task.Run:

public class PopulatePathObjectTask { public PopulatePathObjectTask() { } public int Run() { return 42; } } ... var obj = new PopulatePathObjectTask(); var result = await Task.Run(obj.Run);
您还可以使用 

TaskFactory.StartNew(Func, Object) 重载:

public class PopulatePathObjectTask { public PopulatePathObjectTask() { } public static int Run(object obj) { var path = obj as PopulatePathObjectTask; return 42; } } ... Task.Factory.StartNew(PopulatePathObjectTask.Run, new PopulatePathObjectTask());
但是您可能会注意到,两种替代方案都涉及创建对象,这应该与使用原始 lambda 

() => PopulatePathObject(origin, destination, path)

 没有什么不同,主要区别应该是后者为所有捕获的参数创建一个匿名类。因此,我预计任何替代方案之间几乎没有差异。

但是这个开销首先很大吗? C# 对象分配

。这些对象的大小应该很小(32 字节?),如果对象是“短命的”(即在创建后的第一次GC 中收集),GC 惩罚应该是最小的。我猜想任务调度程序或任务池中的开销将使对象分配开销相形见绌。

但是,您可能想要研究某种类型的闭/开集的

对象池,因为它们可能比一些简单的输入参数大得多。

如果您“真的”需要优化任务创建,您可能需要编写自己的线程代码,因为高度专业化的代码通常可以胜过通用代码。但只有当我确定这是一个重大问题并且具备解决该问题的知识时,我才会这样做。

很高兴,您可以概括 JonasH

0
投票

Task ExecuteActionAsync<TState>( Action<TState> callback, TState state) { return Task.Factory.StartNew(static args => { var local = (ValueTuple<TState, Action<TState>>)args; local.Item2(local.Item1); }, (state, callback)); } Task<TResult> ExecuteFuncAsync<TState, TResult>( Func<TState, TResult> callback, TState state) { return Task.Factory.StartNew<TResult>(static args => { var local = (ValueTuple<TState, Func<TState, TResult>>)args; return local.Item2(local.Item1); }, (state, callback)); }

备注:

为了避免任何额外的基于闭包的开销,

StartNew
  • action
    参数被声明为
    static
    ,它具有编译时保证以避免任何闭包使用。
    您需要将 
    args
  • 转换为
  • ValueTuple
    ,因为 
    StartNew
     的重载使用 
    Object
     作为 
    state
     参数
    据我所知,你不能转换为命名元组,这就是为什么实际的方法调用(
    local.Item2(local.Item1)
  • )看起来这么难看
  • 如果您愿意,您可以在那里添加额外的一行来分解 
  • ValueTuple

var local = (ValueTuple<TState, Func<TState, TResult>>)args;
var (localCallback, localState) = (local.Item2, local.Item1);
return localCallback(localState);

这是一个示例用法
await ExecuteActionAsync(Console.WriteLine, 42);

var res = await ExecuteFuncAsync(_ => _, 42);
Console.WriteLine(res);

Dotnet 小提琴:
https://dotnetfiddle.net/aHyTMA

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.