【问题标题】:Temporary store a Delegate for calling later临时存储一个 Delegate 以供以后调用
【发布时间】:2014-01-17 22:37:17
【问题描述】:

我想在进行异步操作时使用列表将操作存储到框架中。
然后稍后当框架同步时,我将循环处理列表并执行操作。

有没有办法做到这一点,重点是:
- 无反射/动态调用。
- 不为要调用的每个新方法创建类/结构。
- 输入安全。
- 将操作存储在列表中。
- 存储不同种类的方法和参数。
- 稍后执行列表。

我不想使用反射的原因是因为它是一个性能问题。它会非常频繁地使用。
在这种情况下,它与游戏相关,但代码可以作为一个全能使用,如果可以避免反射/DynamicInvoke,则非常适合多线程。

如果这是不可能的,那么还有其他不错的选择吗?

我在代码中做了一个示例,但它使用反射并且不是类型安全的。
基本上步骤是:
1. 使用具有多个不同参数的方法填充列表。
2. 遍历列表并执行所有带有参数的方法。
3. 清除下一个周期的列表。

{
    struct MyDelayedCaller
    {
        public Delegate TheTarget;
        public object[] MyParameters;

        public MyDelayedCaller(Delegate target, object[] parameters)
        {
            TheTarget = target;
            MyParameters = parameters;
        }
    }

    List<MyDelayedCaller> Temporary = new List<MyDelayedCaller>();
    void Update()
    {
        //something happened and another class needs to know
        //but it will have to wait for the sync so as to not cause any treading problems

        Temporary.Add(new MyDelayedCaller(new DelDoSomething1(DoSomething1), new object[] { 10, false }));
        Temporary.Add(new MyDelayedCaller(new DelDoSomething1(DoSomething1), new object[] { 11, true }));

        Temporary.Add(new MyDelayedCaller(new DelDoSomething3(DoSomething3), new object[] { "Some text" }));
        Temporary.Add(new MyDelayedCaller(new DelDoSomething2(DoSomething2), new object[] { 1, 9999, 0.4f }));
    }
    void Sync()
    {
        foreach (var item in Temporary)
        {
            item.TheTarget.DynamicInvoke(item.MyParameters);
        }
        Temporary.Clear();
    }

    delegate void DelDoSomething1(int index, bool alive);
    void DoSomething1(int index, bool alive)
    {

    }
    delegate void DelDoSomething2(int index, int amount, float scale);
    void DoSomething2(int index, int amount, float scale)
    {

    }
    delegate void DelDoSomething3(string text);
    void DoSomething3(string text)
    {

    }
}

【问题讨论】:

    标签: c# multithreading .net-4.0 visual-c#-express-2010


    【解决方案1】:

    我希望我能理解这个问题,因为答案似乎很简单:只需存储一个 List&lt;Action&gt;。你可以在里面放任何你想要的东西。您可以枚举列表并调用所有内容。

    您绝对可以将参数化调用添加到这样的列表:() =&gt; DoSomething1(10, false)。参数封装在Action 中。 C# 编译器会生成一个(强类型)闭包类并为您完成所有这些工作。

    这不是你想要的吗?

    【讨论】:

    • 在循环中使用“优化”时有一个讨厌的错误,您必须为每次迭代声明一个新值,当意外发生时,迟早会导致一些讨厌的错误搜索。除此之外,这是一个很好的答案。
    • C# 5.0 已经纠正了这个陷阱,仅供参考。无论如何,这个延迟的调用者基础设施将代码炸毁了 10 倍,据我所知,它解决了这个单一的陷阱。并可能介绍其他人...如果您想存储调试信息,请使用包装类:class Callable { Action Action; object DebugInfo; }。简单是一个很好的设计目标。
    • 这是将项目转移到 C# 5.0 的又一个充分理由。但是,我并没有真正看到它是如何破坏代码的,因为我可能会犯错误并忘记使用 Create(...) 并直接键入实际方法,但是这些不会发生在我所做的事情上,因为据我所知,但我看不到多线程的任何替代方案,而不会像苍蝇一样在代码中散布锁......无论如何,感谢您提供的信息,非常感谢。
    【解决方案2】:

    我会选择以下内容:

    1. IMyDelayedCaller接口:

      interface IMyDelayedCaller
      {
          void Invoke();
      }
      
    2. MyDelayedCaller 泛型类集:

      class MyDelayedCaller<T1> : IMyDelayedCaller
      {
          private Action<T1> _target;
          public T1 _param;
      
          public MyDelayedCaller(Action<T1> target, T1 parameter)
          {
              _target = target;
              _param = parameter;
          }
      
          public void Invoke()
          {
              _target(_param);
          }
      }
      
      class MyDelayedCaller<T1, T2> : IMyDelayedCaller
      {
          private Action<T1, T2> _target;
          public T1 _param1;
          public T2 _param2;
      
          public MyDelayedCaller(Action<T1, T2> target, T1 param1, T2 param2)
          {
              _target = target;
              _param1 = param1;
              _param2 = param2;
          }
      
          public void Invoke()
          {
              _target(_param1, _param2);
          }
      }
      

      我只展示了最多2个参数,如果需要,您可以制作更多。

    3. 将您的列表更改为List&lt;IMyDelayedCaller&gt;

      List<IMyDelayedCaller> Temporary = new List<IMyDelayedCaller>();
      
    4. 将项目添加到具有编译时类型安全性的列表中:

      Temporary.Add(new MyDelayedCaller<int, bool>(DoSomething1, 10, true));
      Temporary.Add(new MyDelayedCaller<string>(DoSomething3, "Some text"));
      
    5. 使用接口方法调用:

      foreach (var item in Temporary)
      {
          item.Invoke();
      }
      Temporary.Clear();
      

    您可以通过提供允许编译器推断类型参数的静态类使停止 4. 更容易:

    static class MyDelayedCaller
    {
        public static MyDelayedCaller<T1> Create<T1>(Action<T1> target, T1 param)
        {
            return new MyDelayedCaller<T1>(target, param1);
        }
    
        public static MyDelayedCaller<T1, T2> Create<T1, T2>(Action<T1, T2> target, T1 param1, T2 param2)
        {
            return new MyDelayedCaller<T1, T2>(target, param1, param2);
        }
    }
    

    及用法:

    Temporary.Add(MyDelayedCaller.Create(DoSomething1, 10, true));
    Temporary.Add(MyDelayedCaller.Create(DoSomething3, "Some text"));
    

    【讨论】:

    • 你不能用Action替换那个接口吗?
    • @usr 你不能派生自Action。但是您可以列出List&lt;Action&gt;。在发布此答案后,我意识到这就是为什么我使用此解决方案添加了另一个答案。
    • 这很简洁,+ 能够向 MyDelayCaller 添加额外信息以查看何时以及谁调用它进行调试等。
    • 目前唯一的缺点是您没有得到正确的参数名称。
    【解决方案3】:
    You could queue up your inputs and actions in parallel collections:
    
    
    
                var actions = new Dictionary<int, Func<object[], object>>();
                var inputs = new Dictionary<int, object[]>();
    
                //when you want to store the action and it's input
                int counter = 0;
                object[] someObjects = new object[] {};
                actions.Add(counter, x => { return x[0]; });
                inputs.Add(counter, someObjects);
                counter++;
    
    
                //and then later when it's time to execute
                foreach (var input in inputs)
                {
                    actions[input.Key].Invoke(input.Value);
                }
    

    或者,您可以滚动一个存储输入和动作的类,因此输入在执行时通过匹配字典键以外的其他方式与动作耦合。

    【讨论】:

    • 这是一种方法,但它不是类型安全的。感谢您的建议。
    【解决方案4】:

    我的另一个答案显示了非常复杂的方法。然而,有一个更容易。你为什么不列出你的名单List&lt;Action&gt;

    List<Action> Temporary = new List<Action>();
    void Update()
    {
        //something happened and another class needs to know
        //but it will have to wait for the sync so as to not cause any treading problems
    
        Temporary.Add(() => DoSomething1(1, true));
        Temporary.Add(() => DoSomething3("Some text"));
    }
    void Sync()
    {
        foreach (var item in Temporary)
        {
            item.Invoke();
        }
        Temporary.Clear();
    }
    

    应该可以正常工作。

    【讨论】:

    • 这太棒了,正是我需要的。但是,当我编写 for 循环 for(int i = 0; i DoSomething(i, true));对于每个 DoSomething,DoSomething 的 int 被设置为 4。如果我做一个 int a = i; & 将 a 而不是 i 传递给 DoSomething,那么它就是正确的值。在我看来,我可以在没有意识到的情况下犯下严重的错误......对此有什么想法吗?
    • 使用我的其他答案来消除这种可能性。
    【解决方案5】:

    @Eli,您在 for 循环中得到 4 作为输入值,因为操作的执行被推迟了。当它们实际执行时,i = 4;

    为避免这种情况,请将您的输入排队。

    我会用评论回复您的评论,但还没有代表。

    【讨论】:

    • 感谢您向我解释,但我会先回答 MarcinJuraszek,它没有等待发生的这个问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-31
    • 1970-01-01
    • 2021-09-25
    • 1970-01-01
    相关资源
    最近更新 更多