使用 TPL 在多个不同线程上创建单例对象
Posted
技术标签:
【中文标题】使用 TPL 在多个不同线程上创建单例对象【英文标题】:Singleton object creation on multiple different threads using TPL 【发布时间】:2020-10-11 11:14:22 【问题描述】:这只是一个自我学习的情况。我对 TPL 和线程很陌生。无论如何,我正在使用一个通用的 Singleton 类并创建 ~10K 实例来检查我的代码是否每次都返回相同的实例或创建新实例。我在 for 循环中使用任务工厂异步创建实例。为了验证实例的创建,我返回了一个字符串,其中包含这些信息作为字符串列表:
-
迭代计数器
实例名称
实例的哈希码和
线程 ID
并将字符串列表显示到列表框。
我的查询
在跑步中,我发现了一些东西,
-
for 循环中 i 的值因不同的实例而重复
对于那些 10K 迭代,我只创建了 8-9 个线程,而不是预期的 10k 线程。我期待 10K 线程弹出,完成各自的任务,然后优雅地消失。
我可以在我的项目中使用它作为类库,而不管平台是 Web、Windows 还是移动?
请在我的 1OK 线程想法上留言 :)。在多线程方面是好主意还是坏主意?
我的代码
单例类
public sealed class Singleton<T> where T : class
static Singleton()
private Singleton()
public static T Instance get; = Activator.CreateInstance<T>();
类:SingletonInThread
public class SingletonInThread
/// <summary>
/// Method responsible for creation of same instance, 10K times using Task.Factory.StartNew()
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<string>> LoopAsync()
List<Task<string>> list = new List<Task<string>>();
for (int i = 0; i <= 9999; i++)
list.Add(Task.Factory.StartNew(()=> CreateAndLogInstances(i)));
return await Task.WhenAll<string>(list);
/// <summary>
/// Creates new instance of Logger and logs its creation with few details. Kind of Unit of Work.
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
private string CreateAndLogInstances(int i)
var instance = Singleton<Logger>.Instance;
return $"Instancei. Name of instance= instance.ToString() && Hashcode =instance.GetHashCode() && ThreadId= Thread.CurrentThread.ManagedThreadId";
前端 _ 在 UI 方面,在 buttonclick 事件中,填充列表框
private async void button1_Click(object sender, EventArgs e)
IEnumerable<string> list = await new SingletonInThread().LoopAsync();
foreach (var item in list)
listBox1.Items.Add(item);
另外,我注意到在用 10K 项填充列表框时,我的 UI 被阻塞了。请帮我以异步方式填充它。我认识 bgworker、begininvoke 和 methodinvoker。 TPL里除了too还有什么??
输出
---
更新
按照建议,如果我使用 Parallel.For,那么,我得到的不是 10K 字符串,而是 9491、9326 等的随机数字。即小于 10K。不知道为什么???
这是我使用 Parallel.For 的 LoopAsync 方法的更新代码
public IEnumerable<string> LoopAsync()
List<string> list = new List<string>();
Parallel.For(0, 9999, i =>
list.Add( CreateAndLogInstances(i));
);
return list;
【问题讨论】:
必读:csharpindepth.com/Articles/Singleton。此外,使用 async/await 进行并行处理是一个非常奇怪的选择,我建议您改用 Parallel.For 和 Parallel.ForEach 方法 @CamiloTerevinto 请看一看。仍然存在问题 你需要捕获i
:var index = i; list.Add( CreateAndLogInstances(index));
【参考方案1】:
将同一个对象多次添加到 WinForms 列表框中会导致列表框中出现多行,例如:
private void Form1_Load(object sender, EventArgs e)
string foo = "Hello, world";
listBox1.Items.Add(foo);
listBox1.Items.Add(foo);
listBox1.Items.Add(foo);
产生三行声明Hello, world
。因此,在您的示例中收到 10,000 行也就不足为奇了。但它们是同一个对象,还是您创建了多个对象?
我创建了自己的 Logger 类:
public class Logger
static private Random rnd = new Random();
public int Id get; = rnd.Next();
public override string ToString()
return Id.ToString();
确实,每个输出行都有相同的Id
,因此表明在每种情况下都使用了相同的对象实例。您还将调用输出到GetHashCode()
,这在每种情况下也是相同的,表明您很可能只处理一个实例。
【讨论】:
【参考方案2】:for 循环中 i 的值因不同的实例而重复
这与线程/并行/异步或单例实例无关。您会看到这一点,因为闭包捕获变量,而不是值。所以这段代码:
for (int i = 0; i CreateAndLogInstances(i)));
将变量 i
传递给闭包() => CreateAndLogInstances(i)
,而不是i
的当前值。要捕获当前值并在您的闭包中使用它,您需要每个闭包一个单独的变量,如评论中所建议的那样:
for (int i = 0; i CreateAndLogInstances(index)));
对于这 10K 次迭代,我只创建了 8-9 个线程,而不是预期的 10k 线程。我期待 10K 线程弹出,完成各自的任务,然后优雅地消失。
不,您非常不希望这种情况发生。线程的创建和销毁有很多开销。 StartNew
和 Parallel
队列工作到线程池,线程池会快速增长到某个点然后缓慢增长,这是故意的。这是因为,例如,在一台 8 核机器上,拥有 10k 个线程是没有意义的,因为它们无论如何都不能全部运行。
我可以在我的项目中使用它作为类库,而不管平台是 Web、Windows 还是移动?
我从不建议在网络应用程序上使用并行处理,因为您的网络主机已经并行化了您的请求。因此,进行额外的并行处理往往会给您的网络服务器带来负担,并可能使其对其他请求的响应能力大大降低。
另外,我注意到在用 10K 项填充列表框时,我的 UI 被阻塞了。请帮我以异步方式填充它。
您通常希望避免几乎同时进行 10k 次 UI 更新。并行处理对 UI 没有帮助,因为所有 UI 更新都必须在 UI 线程上完成。要么通过一次调用将所有结果放入列表中,要么使用控制虚拟化之类的东西。
【讨论】:
以上是关于使用 TPL 在多个不同线程上创建单例对象的主要内容,如果未能解决你的问题,请参考以下文章