将回调传播到单独的线程

Propagate a callback onto a separate thread

我有一个关于 C# 中的 multithreading 的问题。

假设我们有这个接口

public interface IMyCallback
{
    void OnSum(int a, int b, int result);
}

然后我有另一个 class,它有一个 IMyCallback 作为私有字段的列表

public class MyClass
{
    private IList<IMyCallback> _Subscribers = new List<IMyCallback>();
    
    public void Sum(int a, int b)
    {
        int result = a + b;
        foreach (IMyCallback subscriber in _Subscribers)
        {
            subscriber.OnSum(a, b, result);
        }
    }
}

实际上它所做的是在两个数字之间求和,并通知所有服务订阅者它已经执行了算术运算a + b,结果是结果

现在我想提高性能并将每个通知发送到 线程。为了避免编译器出现外壳和内存问题,我通常会这样做:

public void Sum (int a, int b)
{
    int result = a + b;
    foreach (IMyCallback subscriber in _Subscribers)
    {
         new Thread((o) =>
         {
             object[] state = (object[])o;

             ((IMyCallback)state[0])
                   .OnSum((int)state[1], (int)state[2], (int)state[3]
         })
         {
             IsBackground = true
         }
         .Start(new object[] { subscriber, a, b, result });

    }
}

我想把注意力集中在最后一部分,当我将所有参数传递到对象数组并将其用于委托时。我做得对吗?这是一个正确的模式还是有一些我无法理解的问题?

我对你的问题添加了评论,但会尝试重申我的意思。首先:关于将参数数组作为通用对象传递的问题非常好。因为该方法是唯一使用该通用对象数组的方法,所以它非常有意义。

我有点担心的一件事是您如何生成线程。如果您有 10,000 个订阅者,那么 ol'CPU 会很艰难。由于您当前使用的是同步代码,因此我会在您的订阅者上使用 Parallel.ForEach()

public void Sum (int a, int b)
{
    var result = a + b;
    Parallel.ForEach (_Subscribers, subscriber =>
    {
        subscriber.OnSum(a,b,result);
    });
}

这样做的好处是你的框架决定完成你的工作需要多少线程。如果您有 5 个订阅者或 10,000 个订阅者——它不会杀死您的 CPU.

这段代码的美妙之处还在于,如果 OnSum 抛出一个异常,调用者实际上会得到它。使用您当前的方法,所有异常将永远丢失。

预计到达时间: 现在,如果您想使用 async/await 和 Task,这是 安全 的一种方法:

public Task SumAsync (int a, int b)
{
    return Task.WhenAll(_Subscribers.Select(x=> Task.Run(() =>
    {
        x.OnSum(a, b, a + b);
    })));
}

这也将 catch/throw 所有异常。我 喜欢这个解决方案,因为它不是有机异步的。我喜欢在这个解决方案中使用 Parallel.ForEach()