将回调传播到单独的线程
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()
。
我有一个关于 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()
。