将回调传播到单独的线程上

Propagate a callback onto a separate thread

提问人:user2896152 提问时间:8/1/2020 更新时间:8/1/2020 访问量:152

问:

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

假设我们有这个接口

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

然后我还有另一个类,它有一个作为私有字段的列表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 });

    }
}

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

C# 多线程 闭包

评论

0赞 Andy 8/1/2020
我唯一不喜欢 100% 的是为每个订阅者生成一个线程。如果您最终拥有 1000 名订阅者怎么办?你研究过TPL吗?例如:?顺便说一句:你把对象传递给线程的方式是完全可以的(我的意见)。Parallel.ForEach()
2赞 John Wu 8/1/2020
C# 提供了一些更高级别的构造来处理这些问题。例如,一个多播委托,它允许你从一开始就避免循环,而一个通常比你自己创建线程更可取。你有具体的理由避免这些吗?eventTask

答:

3赞 Andy 8/1/2020 #1

我对你的问题添加了评论,但会尽量重申我所说的话。首先:关于将参数数组作为泛型对象传递的问题完全没问题。因为该方法是使用该泛型对象数组的唯一方法,所以它非常有意义。

我有点担心的一件事是你如何生成线程。如果您有 10,000 个订阅者,那么 CPU 会很粗糙。由于您当前使用的是同步代码,因此我会在您的订阅者上使用 a。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);
    })));
}

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