这纯粹是为了实验目的和/或学习练习.本质上,我想看看是否可以通过创建一个只初始化一次的类来减少使用Task.Run(()=>Func<>())时创建的闭包的占用空间.第一,目标是避免每次运行时都创建一个"新"实例,这可能比我想象的闭包本身效率低(但我知道这仅仅是猜测).因此,创建一个基本类来实现这一点是相当简单的,因为您可以在堆栈中找到示例.

然而,我遇到的问题是,在我看来,如果我想使用另一个类的成员和函数,必须封装它们,或者将它们注入到我们将达到Run的类中,尽管它可能比原始类本身的数据更少,但可能不会有太大的改进.

所以说,我有一些大致的 idea :

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

为了省go 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.
    }
}

我可以用它来访问已经存在的功能?我一直在考虑为打开/关闭 node 集合创建静态成员并使用依赖项注入,但我认为,或者更确切地说,希望有人可能对此有更深入的了解,除了它毫无意义,甚至possible的开销减少或性能yield 将是如此微不足道,以至于它毫无意义.当然,你可能是对的,但我这样做只是一种练习,我希望能够真正衡量其中的差异.我甚至可能不会使用它,甚至可能会抛弃AStar而转而使用JPS,但在继续之前我想知道.我不能完全确定,但似乎关闭必须及时捕获整个AStar对象,人们希望通过引用.

推荐答案

令人欣慰的是,您可以将mentioned by JonasH的概念概括为

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‘S action参数被声明为static,它有编译时间保证以避免任何闭包使用.
  • 您需要将args转换为ValueTuple,因为StartNew‘S重载使用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


将其应用于您的用例

record struct PopulatePathArgs(Vector2Int origin, Vector3Int destination, PathObject path);

...
public PathObject PopulatePathObject(PopulatePathArgs args)

...

var result = await ExecuteFuncAsync(PopulatePathObject, new PopulatePathArgs(...));
//or more verbosely 
var result = await ExecuteFuncAsync(_ => PopulatePathObject(_), new PopulatePathArgs(...));

UPDATE #1

我不知道我们可以对lambda函数应用静态修饰符

此功能是在C#9:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/static-anonymous-functions中引入的

然而,我能想到的或看起来的一切,总是在某个地方有"新"的.

既然您使用的是面向对象的编程语言,那么这对新事物来说应该不是什么大事.即使从methodA调用methodB,运行库也会分配内存,因为会创建一个新的StackFrame.

如果分配的变量的作用域是函数,这意味着只要您离开函数,它们就会被清除.如果您有闭包,那么您希望访问原始函数作用域之外的变量.这就是(代表您)创建匿名类来捕获这些变量的原因.因此,使用闭包可以延长变量的生命周期,并在堆上分配封闭的类.该对象稍后将由GC清理.

JonasH和MINE的解决方案都避免使用闭包,因此,分配的内存在函数退出时被清理.所以,不用担心内存分配问题.

另一方面,提出的解决方案的缺点是您必须定义一个封闭的数据 struct (记录、类、 struct 等).

我还读到了Disard操作符可以防止内存分配,但我还没有测量它的影响.

如果您愿意,您也可以在那里使用static:

var res = await ExecuteFuncAsync(static _ => _, 42);

如果愿意,您还可以定义标识函数,如下所示:

public static class HelperFunctions
{
    public static T Identity<T>(T value) => value;
}
...

var res = await ExecuteFuncAsync(Identity, 42);

Csharp相关问答推荐

如何将字节数组转换为字符串并返回?

System.Text.Json数据化的C#类映射

在多对多关系上不删除实体

ASP.NET Core 8.0 JWT验证问题:尽管令牌有效,但SecurityTokenNoExpirationError异常

在C#中,DirectoryEntry返回空AuditRules集合,即使审计规则确实存在

异步实体框架核心查询引发InvalidOperation异常

为什么C#认为这个非托管 struct 有一个重叠

当索引和外键是不同的数据类型时,如何设置导航属性?

从VS调试器而不是测试资源管理器运行的调试NUnitDotNet测试

EFR32BG22 BLE在SPP模式下与PC(Windows 10)不连接

源代码生成器项目使用`dotnet build`编译,而不是在Visual Studio中编译?

有条件地定义预处理器指令常量

Azure Functions v4中的Serilog控制台主题

如何使用Npgsql从SELECT获得所有查询结果

如何在发布NuGet包之前设置命名空间?

类/值和日期的泛型方法

C# Winforms:从对象树到TreeView的递归转换重复条目

并发表更新.EF核心交易

使用SQL Server 2022+时,我是否必须将代码从SqlConnection类对象中迁移出来?

在使用xUnit和Mock执行单元测试时,控制器ViewResult返回空的Model集合